From d9489aa9d6ca3de4dc99e67fa42e6a0f20a2f8d1 Mon Sep 17 00:00:00 2001 From: aecook <36861760+aecook@users.noreply.github.com> Date: Thu, 2 Mar 2023 08:40:55 -0800 Subject: [PATCH] PFG-1891 - Update Prebid to 7.37 (#153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updated the correct variable while setting cookie (#9234) Co-authored-by: pm-azhar-mulla * Smartx Bid Adapter: Add Schain support (#9244) * Add smartclipBidAdapter * smartxBidAdapter.js - removed unused variables, removed debug, added window before the outstream related functions * - made outstream player configurable * remove wrong named files * camelcase * fix * Out-Stream render update to SmartPlay 5.2 * ESlint fix * ESlint fix * ESlint fix * adjust tests, fixes * ESlint * adjusted desired bitrate examples * added bid.meta.advertiserDomains support * bug fix for numeric elementID outstream render * fix renderer url * support for floors module * bugfixes to be openRTB 2.5 compliant * update internal renderer usage * remove unused outstream_function logic * bugfix outstream options for default outstream renderer configuration * [PREB-10] fix empty title not configurable * add pbjs version * testing with outstream 5.3.0 * pbjs version into content.ext * made visibilityThreshold configurable * adjust position of pbjs version * Merge branch 'master' of https://github.com/prebid/Prebid.js into HEAD * update smartclip outstream player version to support outstream 6 release along with necessary config changes * Add support for schain * vacuuming Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini Co-authored-by: smartclip-adtech <65160328+smartclip-adtech@users.noreply.github.com> * Fix to merge site fpd into payload as opposed to overwriting (#9247) * Discovery Bid Adapter : parameter updates (#9249) * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter Co-authored-by: BaronYu * Smartadserver Bid Adapter: add support for SDA user and site (#9231) * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing Co-authored-by: Krzysztof Sokół <88041828+smart-adserver@users.noreply.github.com> * VidazooBidAdapter: get bid floor using `bid.getFloor` (#9238) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * added bid.getFloor handler Co-authored-by: roman Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: Saar Amrani * Viqeo Bid Adapter: initial adapter release (#8920) * add viqeo prebid adapter * added bid params to docs * updated to Outstream * updated to Outstream (tests) * BeOp Bid Adapter : update keywords management (#9166) * Don't know why params are in an array in that bid object. Make it work for both if it is fixed later * Update params reading method to adapt to arrays for all params * Keywords have to be an array of string (#9) * Keywords have to be an array of string * Last check if isStr * Fix linting errors * Fix tests * TheMediaGrid: added withCriteo paramater to send criteo request with the /hbjson request (#9214) * Tappx Bid Adapter: getting correct site page (#9187) * Fix: creating host correctly when http or https are added from the beginning * Fix :: Changed double quotes for single quotes * Fix :: Getting the full page URL * Fix :: Changed order params * Fix :: Replaced quotes from double to simple * Fix :: Adapting format to lint * Remove TODO comment * Added more controls * camelcase fix * Changed test * Remove "inIframe" util Co-authored-by: Jordi Arnau Co-authored-by: ruben_tappx * Prebid 7.26.0 release * Increment version to 7.27.0-pre * Bump loader-utils from 2.0.3 to 2.0.4 (#9256) Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/webpack/loader-utils/releases) - [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md) - [Commits](https://github.com/webpack/loader-utils/compare/v2.0.3...v2.0.4) --- updated-dependencies: - dependency-name: loader-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * glomex Bidder: expose glomex GVL id (#9262) * Update Floor format to floor={adslotId}:{floorPriceInCents}[, ...] and fix the size which is submitted to the Price Floors Module (#9186) * Gravito Id System : variable update to fix tests (#9259) * gravitompId user module for integrating gravito first party cookie with prebid js * fixed eslint issues raised by circleci * fixed trailing spaces error raised by circleci * Merge branch 'master' of https://github.com/GravitoLtd/Prebid.js * minor changes to GravitoIdSystem user module to make sure that Gravito Id is visible in bid request. * changes to Gravito User module to fix issue with Gravito Id not appearing in bid request. * fixing issue with Gravito Id not appearing in bid request. * modified test case to adapt changes made to the GravitoIdSystem user module * change to spec file to fix for test failure * changes to GravitoIdSystem spec file. * Fix for trailing space issue in Circle CI * AdMatic Bidder: added User-Snyc url for alias (#9261) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Browsi RTD Module: add pageview billable event (#9207) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * fire billable event according to event listener * RTB House Bid Adapter: Process FLEDGE request/response (#9215) * RTBHouse Bid Adapter: add global vendor list id * structured user agent - browsers.brands * fix lint errors * Added sda into rtbhouse adapter * spreading ortb2: user & site props * examples reverted * init version * using mergedeep * removed wrong imp array augm.; slot imp augm. with addtl check * [SUA] merging ortb2.device into request * fledge auctionConfig adapted to our bid response structure * new bidder response structure for fledge * make sure bidderRequest has proper flag turned on * fledge endpoint hardcoded; code cleanups * remove obsolete function * obsolete function removed * [RTB House] Process FLEDGE request/response (#4) * [SDA & SUA] refactor using mergedeep * [FLEDGE] fledge auctionConfig adapted to our bid response structure * [FLEDGE] new bidder response structure for fledge * [FLEDGE] make sure bidderRequest has proper flag turned on * [FLEDGE] fledge endpoint hardcoded; code cleanups * [FLEDGE] remove obsolete functions * fixed lint errors * fledge test suites; adapter: delete imp.ext.ae when no fledge (#5) Co-authored-by: Leandro Otani Co-authored-by: rtbh-lotani <83652735+rtbh-lotani@users.noreply.github.com> Co-authored-by: Tomasz Swirski * Bump engine.io from 6.2.0 to 6.2.1 (#9270) Bumps [engine.io](https://github.com/socketio/engine.io) from 6.2.0 to 6.2.1. - [Release notes](https://github.com/socketio/engine.io/releases) - [Changelog](https://github.com/socketio/engine.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1) --- updated-dependencies: - dependency-name: engine.io dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Rise readme maintenance (#9272) * Criteo Bid Adapter : fix getFloor usage issue (#9243) getFloor call context is loss when we reference it as a callback ; we need to make sure that it is properly mapped as getFloor function assumes that "this" is the actual bid request object * Rise Bid Adapter: add support for mimes, api, protocols in bid object (#9253) * added support to mimes, api and protocols based on bid settings * moved protocols only if object is video * updated path for protocols property * added unit test for api, mimes and protocols properties * Prebid core: fix CPM to always be a number (#9273) * nexx360 Bid Adapter: new functionalities and endpoint update (#9229) * bidder version v1.0 * Comments removed * Test coverage improvement above 80% * ePlanning Bid Adapter : fix support for video auction (#9283) * Fix ad vast * fix lint spaces in tests * fix lint spaces in tests * fix lint spaces in tests * add smn alias (#9290) * Smartx Bid Adapter: update custom header (#9291) * Add smartclipBidAdapter * smartxBidAdapter.js - removed unused variables, removed debug, added window before the outstream related functions * - made outstream player configurable * remove wrong named files * camelcase * fix * Out-Stream render update to SmartPlay 5.2 * ESlint fix * ESlint fix * ESlint fix * adjust tests, fixes * ESlint * adjusted desired bitrate examples * added bid.meta.advertiserDomains support * bug fix for numeric elementID outstream render * fix renderer url * support for floors module * bugfixes to be openRTB 2.5 compliant * update internal renderer usage * remove unused outstream_function logic * bugfix outstream options for default outstream renderer configuration * [PREB-10] fix empty title not configurable * add pbjs version * testing with outstream 5.3.0 * pbjs version into content.ext * made visibilityThreshold configurable * adjust position of pbjs version * Merge branch 'master' of https://github.com/prebid/Prebid.js into HEAD * update smartclip outstream player version to support outstream 6 release along with necessary config changes * Add support for schain * vacuuming * update custom header x-openrtb-version to 2.5 Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini Co-authored-by: smartclip-adtech <65160328+smartclip-adtech@users.noreply.github.com> * LimeLight Bid Adapter : add IionAds alias (#9285) * User sync improvements * User sync improvements * Code review fixes * Pass supply chain to limelightDigitalBidAdapter.js * Add alias for iionads Co-authored-by: apykhteyev Co-authored-by: EngineeringProjectLimeLight <45598299+EngineeringProjectLimeLight@users.noreply.github.com> * Generic Analytics Adapter: initial release (#9134) * New module: generic analytics adapter * Use special gvlid value instead of `isVendorless` flag for vendorless consent checks * Mark generic analytics as vendorless for gdpr enforcement * Allow analytics adapters to define dynamic gvlids * Add gvlid option * Gdpr enforcement softVendorExceptions * GrowthCode Analytics Adaptor Module: initial module release (#9021) * Initial check-in ofthe GrowthCode Adaptor * Growthcode ID System * Working on test module * Tests for the growthCode Id System * Clean up tests for GrowthCode * Fixed the default values for shareID * New Analyics package * Growthcode Analyics Adapter * Backout growthcode User ID module * Yieldlift Bid Adapter: update ttl (#9232) * Updating TTL, changing endpoint * Test fixed Co-authored-by: Danijel Predarski * Multiple analytics modules: allow pub-defined event filters; do not block auction for analytics (#9113) * Multiple analytics modules: allow pub-defined event filters for analytics * AnalyticsAdapter: make event handling non-blocking * Fix infinite recursion on nested events * Exclude AUCTION_DEBUG by default * Use a default whitelist intead of default blacklist * Try to detect and break out of infinite loops caused by events triggering other events * JW Player RTD Module: prefer segment.id to segment.value (#9153) * removes id * updates docs * Revert "updates docs" This reverts commit 926a06dd9581868e6fd9e682e68b48592aebbd3e. * Revert "removes id" This reverts commit c3a1be70e7274451b6b60381c036385b579eb23b. * drop segment.value * AcuityAds adapter: fix issue with download (#9164) * add prebid.js adapter * changes * changes * changes * changes * fix downolad * AIDEM Bid Adapter: initial adapter release (#9222) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object Co-authored-by: darkstar * Ringier Axel Springer Bidder Adapter (#9239) - New parameter `customParams` Co-authored-by: skoklowski * updated ref info page logic (#9241) * fix for broken download bundle https://github.com/prebid/prebid.github.io/issues/4177 (#9289) * Yandex Bid Adapter: (#9280) * refactoring; * added banner.format to payload; * added tmax support; * fixed nurl sending; * added support for the block identifier format in the Yandex Ad system; Co-authored-by: Taras Saveliev * Fluct Bid Adapter: add schain support (#9266) * add schain to req * run circleci Co-authored-by: Chris Huie * Update bucksense adapter - new server endpoint (#9292) * Seedtag Bid Adapter : add support for inBanner and inStream (#9230) * use inBanner and inStream for video * remove duplicate video params, now use only params from adunit level * lint * improve unit test * fix adapter for instream support, and fix unit test * use inStream placement for instream context * use ALLOWED_DISPLAY_PLACEMENTS * fix lint error * empty commit to relaunch CI * Geoedge RTD module: support billing events (#9267) * Add billable events for applicable winning bids * Update test for billable events * Add meta bid advertiser domains collection * Revert "Add meta bid advertiser domains collection" This reverts commit 09c19c90b4dc9d234f282de8adb40850cce31101. * Add meta bid advertiser domains collection * Update geoedgeRtdProvider_spec.js Force circleci Co-authored-by: daniel manan Co-authored-by: Patrick McCann * Vidazoo Bid Adapter: added bid request params (gpid, cat, pagecat) (#9293) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * added bid request params * making the linter happy :) Co-authored-by: roman Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: Saar Amrani * PBjs Core : send native targetings for ortb response (#9252) * PBjs Core : send native targetings for ortb response * Add legacy native properties regardless of response mediaType * add a test for addLegacyFieldsIfNeeded * fix lint for test Co-authored-by: Demetrio Girardi * Impactify Bid Adapter: add support for BidFloor (#9277) * Add support of getFloor function * Add support of getFloor function * Add support of getFloor function * Add support of getFloor function * Add unit test for bid floor * Add unit test for bid floor Co-authored-by: Thomas De Stefano * TTD Bid Adapter: add support for regs.gpp (#9274) * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter_spec.js * fix linting * Update ttdBidAdapter_spec.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter.js * Update ttdBidAdapter_spec.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js * Update ttdBidAdapter.js Co-authored-by: Chris Huie * Revert "fix for broken download bundle https://github.com/prebid/prebid.github.io/issues/4177 (#9289)" (#9298) This reverts commit 5a9aaa8ca5bb47393ddfb08b00d1c4e0af5fd850. * Revert "AcuityAds adapter: fix issue with download (#9164)" (#9299) This reverts commit 4f21a5bc9e2e4ace066a4d26ca1c9c637c46a477. * Add new size 192x160 (ID: 622) in Rubicon Adapter (#9297) * Redtram Bid Adapter : initial adapter release (#9260) * Add Redtram Bid Adapter * add on bidWon test * extend tests * remove convertOrtbRequestToProprietaryNative 9260#pullrequestreview-1196218534 Co-authored-by: Oleh Naimushyn * VidazooBidAdapter: sending storageAllowed flag with request params (#9294) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * added bid params to request Co-authored-by: roman Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: Saar Amrani * Triplelift Adapter: Update referrer logic (#9304) * prioritize topmostlocation * adds test for topmostlocation / referrer * cleanup * delete param after test * TL-32803: Update referrer logic * TL-32803: Update referrer logic Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> * Prebid 7.27.0 release * Increment version to 7.28.0-pre * ttd Bid Adapter: add regression test topmost domain (#9300) * TTD Adapter use topmost location when available * TTD add use top most location regression test Co-authored-by: Andre Gielow * Kargo Adapter: Update referrer logic (#9305) * pageURL pull from topmostLocation * Kargo: Support for client hints (#9) * Starting SUA support * Kargo: Adding support for client hints * Adding tests for sua * Kargo: Update referer logic * Discovery Bid Adapter & Mediago Bid Adapter: add support for test request param (#9302) * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter * Discovery Bid Adapter : parameter updates * Mediago Bid Adapter : parameter updates * Mediago Bid Adapter : code style format * rerun circleci * rerun circleci * rerun circleci * rerun circleci Co-authored-by: BaronYu * OpenX Bid Adapter: update documentation about deprecated platform and hint for using floor module (#9308) * Bump decode-uri-component from 0.2.0 to 0.2.2 (#9311) Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2. - [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases) - [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2) --- updated-dependencies: - dependency-name: decode-uri-component dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * OpenX Bid Adapter: fix bid parameters table in documentation (#9310) * Bump tibdex/github-app-token from 1.6.0 to 1.7.0 (#9316) Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 1.6.0 to 1.7.0. - [Release notes](https://github.com/tibdex/github-app-token/releases) - [Commits](https://github.com/tibdex/github-app-token/compare/f717b5ecd4534d3c4df4ce9b5c1c2214f0f7cd06...021a2405c7f990db57f5eae5397423dcc554159c) --- updated-dependencies: - dependency-name: tibdex/github-app-token dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Vidazoo Bid Adapter: support for Video MediaTypes (#9284) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(client): added VIDEO media type * feat(client): send mediaTypes to the server * feat(client): added vastXml support * fix(client): vidazoo adapter tests * fix tests * remove console.log from test file * added video tests Co-authored-by: roman * Vidazoo bid adapter: fix failing test (#9318) * Live Intent User ID Submodule: Bump live-connect version (#9317) * update with live-connect last change * set globalVarName * not use globalVarName * use live-connect proper version * comment * use yalc version and adjust the initializer * adjust getInitializer * use a proper lc version * ox update (#9309) * AdHash bid adapter: update to support latest version (#9286) * AdHash Bidder Adapter: minor changes We're operating on a com TLD now. Added publisher in URL for easier routing. * Implemented brand safety Implemented brand safety checks * Fix for GDPR consent Removing the extra information as request data becomes too big and is sometimes truncated * Ad fraud prevention formula changed Ad fraud prevention formula changed to support negative values as well as linear distribution of article length * AdHash brand safety additions Adding starts-with and ends-with rules that will help us with languages such as German where a single word can be written in multiple ways depending on the gender and grammatical case. * AdHash brand safety updates Added support for Cyrillic characters. Added support for bidderURL parameter. Fixed score multiplier from 500 to 1000. * AdHash Analytics adapter * Support for recent ads Support for recent ads which gives us the option to do frequency and recency capping. * Fix for timestamp * PUB-222 Added logic for measuring the fill rate (fallbacks) for Prebid impressions * Unit tests for the analytics adapter Added unit tests for the analytics adapter * Removed export causing errors Removed an unneeded export of a const that was causing errors with the analytics adapter * Added globalScript parameter * PUB-227 Support for non-latin and non-cyrillic symbols * GEN-964 - Brand safety now checks the page URL for bad words. No ad is shown if there is at least one match. - Repeating code is optimized and moved to helper function - Multi-language support for brand safety * GEN-1025 Sending the needed ad density data to the bidder * Removing the analytics adaptor * Fix for regexp match * Version change * MINOR Code review changes Co-authored-by: NikolayMGeorgiev Co-authored-by: Ventsislav Saraminev Co-authored-by: Dimitar Kalenderov * Add source and version parameters to the 33across ID request (#9319) * Prebid Core: ORTB 2.5 translation utilities (#9263) * ORTB 2.5 spec definition * ORTB 2.5 translation * Only test translation of native reqs if FEATURES.NATIVE is set * Prebid Core: Addition of Optional Category Targeting Key (#9268) * addition of category optional targeting * removed console log statements * console.log statements for debugging * updated tests * formatting changes * added pbs test * Taboola Bid Adapter: Fixing Accepting Bid Floor Mechanism (#9279) * use-convention-for-bidfloor-extraction * use-convention-for-bidfloor-extraction * add-unit-tests * Prebid 7.28.0 release * Increment version to 7.29.0-pre * Colossus Bid Adapter: update user sync (#9327) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk * PubMatic Analytics Adapter:added parameters in logger call (#9328) * Logging floor related params in loger * Adding au and mt parameters in logger and tracker call * Added extra check Co-authored-by: pm-azhar-mulla Co-authored-by: pm-priyanka-deshmane * Nextmillenium bid adapter: Collection of statistics data (#9265) * changed name company * changed name company in test * Added processing of a new group_id parameter * Added processing of a new group_id parameter * changed check parameters * fixed lint remarks * added test * fixed bug - lint * changed test * changed test - 2 * fixed bug - adapter * added logic for getting ad impressions * Collecting timeouts data * Collecting resaponses and no_bids data * changed a name function * added event bidRequested * added event bidRequested * added function initialization events * fixed bug * save * added tests * Added processing of the disabledSendingStatisticData parameter, which disables sending statistics data * changed the name of the variables * Video Module: Ad Queueing (#9226) * adds queue coordinator * tests module * test setAdTag * tests integration * updates examples * documents the event * loads get queued always * updates tests * decouples register from init * updates tests * Revert "updates tests" This reverts commit 1616dfad2bba34a10b3f20fb5680baea7ce11fa1. * updates tests * Adnuntius Bid Adapter: native added (#9330) * package lock fix. * Add dimensions to prebid. * Adnuntius Bid Adapter. Added native as a media type. * Prebid core: enrich FPD by default (#9205) * Move rootDomain * Move FPD enrichments to core * Remove fpdEnrichments module * Cleanup * Add `site`, `device`, and coppa enrichments * FPD enrichments: GDPR * FPD enrichments: USP * Enrich FPD from requestBids * Fix typo in package.json * Permutive RTD Module: add support for new ssp standard cohorts (#9236) * add logic to parse and pass ssp data to appnexus * simplify shouldSetConfig condition * Write SSP cohorts into p_standard targeting (+ bug fixes) * fix tests * Simplify * add logic to parse and pass ssp data to appnexus * simplify shouldSetConfig condition * Write SSP cohorts into p_standard targeting (+ bug fixes) * fix tests * Simplify * use new key for auction kw cohorts * Push SSP cohorts to SSPs via ORTB2 * Add tests and fix bugs * Update tests * update example with _pssps * Remove custom `appnexusAuctionKeywords` and use user.keywords in ortb2 config * Fix linting issues Co-authored-by: Paulius Imbrasas * Jixie Bid Adapter: Add read jxtoko cookie (#9331) * Adapter does not seem capable of supporting advertiserDomains #6650 added response comment and some trivial code. * removed a blank line at the end of file added a space behind the // in comments * in response to comment from reviewer. add the aspect of advertiserdomain in unit tests * added the code to get the keywords from the meta tags if available. * added some cookie fetching * AdagioBidAdapter: add missing try-catch (#9338) * AdUp Technology bid adapter: optimize floor price detection (#9332) * nextMillenniumBidAdapter: improve getUserSyncs function (#9313) * add video support * improve userSync url * improve userSync url * Add tests for a new cases * use deepAccess instead of instanceof * Uid2 module: major implementation change (#9264) * Complete the UID2 integration. Update docs. Add tests. * Removed some unnecessary code in uid2IdSystem.uid2IdSystem. Improved log messages. Pass through configured baseUrl. Tidied up some in-progress code problems. Added a timer mock to track and clear timers at the end of each test, to prevent interference. Improved testing code and fixed some bugs. * Move cookie cleanup into the after so it doesn't leave a mess behind for subsequent tests. Allow specifying multiple --file options when running/watching tests. * Provide an additional mock object for some test environments which don't provide crypto.subtle. * Improve some documentation for the UID2 module. * Improved UID2 module logging when debug flag is enabled. * Added tests around the api base url config for UID2. Added the new UID2 config to the example. * Update integration example to attempt a token refresh (it will fail due to not being a valid token). * Refactor to avoid duplicating cookie read code. Add a test for the case when the id value is provided directly in config without making use of the new token refresh system. * Fix an incorrect log call. Co-authored-by: Lionell Pack * Globalsun Bid Adapter: Initial Release (#9307) * init new adapter Globalsun * kick off integration tests Co-authored-by: Chris Huie * Topics module: Initial Topics iframe implementation (#8947) * Topics: Initial Topics iframe implementation * Topics API: LINT errors solved * Added Empty Topics Check * Topics: Storage Map logic and added message listener secure check * Topics: Iframe implementation for bidders * Added topics_iframe html in example for reference * Added Pubmatic Topic iframe URL * Added Pubmatic Topic iframe URL- Removed comment * Topics Module: Consent management logic added * Topics Module: Added Device Access check * Topics Module: Unit test cases added and minor changes * Topics Module: Array.find used instead of array.some and variable name changed * Topics IFrame Implementation: Purpose present check is handled * Nativo Bid Adapter: added ntv_url qs param value validation (#9334) * Initial nativoBidAdapter document creation (js, md and spec) * Fulling working prebid using nativoBidAdapter. Support for GDPR and CCPA in user syncs. * Added defult size settings based on the largest ad unit. Added response body validation. Added consent to request url qs params. * Changed bidder endpoint url * Changed double quotes to single quotes. * Reverted package-json.lock to remove modifications from PR * Added optional bidder param 'url' so the ad server can force- match an existing placement * Lint fix. Added space after if. * Added new QS param to send various adUnit data to adapter endpopint * Updated unit test for new QS param * Added qs param to keep track of ad unit refreshes * Updated bidMap key default value * Updated refresh increment logic * Refactored spread operator for IE11 support * Updated isBidRequestValid check * Refactored Object.enties to use Object.keys to fix CircleCI testing errors * Updated bid mapping key creation to prioritize ad unit code over placementId * Added filtering by ad, advertiser and campaign. * Merged master * Added more robust bidDataMap with multiple key access * Deduped filer values * Rolled back package.json * Duped upstream/master's package.lock file ... not sure how it got changed in the first place * Small refactor of filterData length check. Removed comparison with 0 since a length value of 0 is already falsy. * Added bid sizes to request * Fixed function name in spec. Added unit tests. * Added priceFloor module support * Added protection agains empty url parameter * Changed ntv_url QS param to use referrer.location instead of referrer.page * Removed testing 'only' flag * Added ntv_url QS param value validation * Prebid 7.29.0 release * Increment version to 7.30.0-pre * Build system: set up `hook` for tests (#9350) * add encoding for device param (#9352) * OneTag Bid Adapter: add use of refererInfo Prebid object and Network API (#9306) * OneTag Bid Adapter: add use of refererInfo Prebid object and Network Information API * Replace refererInfo.location with refererInfo.page Co-authored-by: federico * TheMediaGrid: fix tmax value (#9339) * Viously Bid Adapter : New Adapter (#9076) * Add viously Bid Adapter * Mod: viously documentation * MR fixes * Topics FPD module: fix tests (#9354) * Ccx Bid Adapter: Add GVLID param (#9359) * adomain support * adomain support * adomain support * adomain support * adomain support * video params * docs changes * Clickonometrics adapter update * Revert "Revert "Clickonometrics Bid Adapter : add gvlid (#9198)" (#9216)" This reverts commit 6d114e83725b403fadd889202b449de225db7275. * Revert "Ccx Bid Adapter: Add GVLID param (#9359)" (#9363) This reverts commit 77647180e5fc3c8058adcf5d642499ad3146b495. Co-authored-by: Demetrio Girardi * GPP consent module: phase one release (#9321) * GPP consent module phase 1 * various updates and added test pages * revise calling CMP, remove provisionalConsent, remove cmpDisplayStatus check, update pbs usersync * change callback check to be more strict * update logic on adding gpp data to ortb2 * update gpp metadata * Magnite Analytics Adapter : data deletion function (#9351) * add onDeletionRequest functionality to Magnite adapter * Magnite add onDataDeletionRequest unit testing * Colosuss Bid Adapter: add support First Party Data (#9340) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk * Clickonometrics Bid Adapter: gvlid (#9367) * adomain support * adomain support * adomain support * adomain support * adomain support * video params * docs changes * Clickonometrics adapter update * Revert "Revert "Clickonometrics Bid Adapter : add gvlid (#9198)" (#9216)" This reverts commit 6d114e83725b403fadd889202b449de225db7275. * Test fix * Prebid 7.30.0 release * Increment version to 7.31.0-pre * Bump parse-url from 7.0.2 to 8.1.0 (#9372) Bumps [parse-url](https://github.com/IonicaBizau/parse-url) from 7.0.2 to 8.1.0. - [Release notes](https://github.com/IonicaBizau/parse-url/releases) - [Commits](https://github.com/IonicaBizau/parse-url/compare/7.0.2...8.1.0) --- updated-dependencies: - dependency-name: parse-url dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Prebid core: filter adUnits (by `adUnitCodes`) before sending them to RTD and FPD modules (#9355) * Prebid Server: Include adUnitCode in PBS Adapter Requests (#9337) * changes * added support to pass adunitcode to pbs * updated comment * removed console log statements * addressed feedback * Feedad Bid Adapter: fixed usersync parsing (#9353) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter * added decoration parameter to adapter documentation * added pass through of additional bid parameters * added user syncs to FeedAd bid adapter * increased FeedAd bid adapter version * lint pass over FeedAd bid adapter * fixed parsing of user syncs from server response * increased FeedAd bid adapter version * fixed version code in test file * Datawrkz adapter: Using bidRequest.getFloor() method for bid floor (#9366) * New Bid Adapter: datawrkz * New Bid Adapter: datawrkz. Test case formatting * New Bid Adatpter: datawrkz - updated import statements * Datawrkz adapter: Using bidRequest.getFloor() method for bid floor * Adkernel Bid Adapter: bidbuddy.co.in alias (#9375) * Confiant RTD Module : initial release (#9325) * Confiant's RTD Provider Module * Confiant RTD Module: - updated script injection code to current standard - added Confiant as an exclusion to load external JS * Confiant RTD Provider: - additional param for enabling BillingEvent added - docs updated - outdated unit test removed Co-authored-by: Patrick McCann * Prebid 7.31.0 release * Increment version to 7.32.0-pre * Confiant RTD Provider: (#9382) - fix comment line * Rise Bid Adapter: added isWrapper parameter to adapter request (#9329) * add Rise adapter * fixes * change param isOrg to org * Rise adapter * change email for rise * fix circle failed * bump * bump * bump * remove space * Upgrade Rise adapter to 5.0 * added isWrapper param * addes is_wrapper parameter to documentation * added is_wrapper to test * removed isWrapper Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> * Added video media type support (#9326) * Aso Bid Adapter: add bcmint alias (#9387) * Add bcmint alias * kick off tests Co-authored-by: dev Co-authored-by: Chris Huie * AIDEM Bid Adapter: added wpar and placementId param (#9377) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object Co-authored-by: darkstar Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> * Taboola Bid Adapter: onBidWon, userSyncs, gpp support and FPD (#9376) * on-bid-won * support-fpd * support-fpd * support-fpd * support-fpd * support-fpd * support-fpd * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * implement-get-user-sync * position-pagetype * Yieldlab Bid Adapter: read and pass UserIdsAsEids atype information (#9370) * YieldlabBidAdapter read atype information from UserIdsAsEids and pass it as query parameter (atypes={idprovider}:{atype},{idprovider2}:{atype2},...) * Update type hint and add semi colons Co-authored-by: Christoph Kipping <29540638+kippsterr@users.noreply.github.com> * Medianet RTD module: fix `getTargetingData` to retrieve correct adUnits (#9392) * Holid Bid Adapter: initial release (#9371) * Holid bid adapter * Adjust test to various device sizes * Include first party data from ortb2 object * Remove trailing spaces in test * Appnexus Bid Adapter : add video data from the request to the bid response (#9396) * Appnexus adapter: add video data from the request to the bid response * kick off tests * remove change Co-authored-by: Chris Huie * AdagioBidAdapter: Remove some params (#9398) * Feedad Bid Adapter: added new bid request parameters (#9397) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter * added decoration parameter to adapter documentation * added pass through of additional bid parameters * added user syncs to FeedAd bid adapter * increased FeedAd bid adapter version * lint pass over FeedAd bid adapter * fixed parsing of user syncs from server response * increased FeedAd bid adapter version * fixed version code in test file * added adapter and prebid version to bid request parameters * removed TODO item * added missing test case for user syncs * increased adapter version to 1.0.5 * Yieldlab Bid Adapter: code style updates (#9386) * Consistently add trailing comma and semicolons everywhere * Use shorthand object property function definition * Fix typo and update type hint * GPP support for the yahoo connect id module. (#9399) Co-authored-by: dumitrubarbos * yahoospp bidder& aol bidder: GPP Support in bid requests (#9345) * GPP support for the yahoospp bidder and legacy aol bidder. * GPP support for the yahoospp bidder and legacy aol bidder. * GPP support for the yahoo connect id module - review comments. * GPP support for the yahoo connect id module - review comments. * GPP support for the yahoo connect id module - review comments. Co-authored-by: dumitrubarbos * Orbitsoft Bid Adapter : add adapter back to current version (#9288) * Adding Orbitsoft module * Adding Orbitsoft module (corrected) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to new constructor) * Adding Orbitsoft module (delete unnecessary aliases) * Adding Orbitsoft module (delete unnecessary aliases) * fixed orbitsoftAdapter * fixed orbitsoftAdapter processing undefined request referrer * fixed orbitsoftAdapter processing undefined request referrer * fix-orbitsoftAdaper: codereview fixes * added changes for new spec * added changes for new spec * added changes for new spec Co-authored-by: Dmitriy Shimko Co-authored-by: Хатламаджиян Виталий * extract-gpid (#9401) * Yieldmo Adapter: Add support for structured user agent (#9380) * Adding sua to device object * Update * import pick * ESLint fixes * Adding unit test * Copying entire device object * appnnexus bid adapter - support for adomain (#9403) * smartx Bid Adapter: add support for sitekey (#9408) * Add smartclipBidAdapter * smartxBidAdapter.js - removed unused variables, removed debug, added window before the outstream related functions * - made outstream player configurable * remove wrong named files * camelcase * fix * Out-Stream render update to SmartPlay 5.2 * ESlint fix * ESlint fix * ESlint fix * adjust tests, fixes * ESlint * adjusted desired bitrate examples * added bid.meta.advertiserDomains support * bug fix for numeric elementID outstream render * fix renderer url * support for floors module * bugfixes to be openRTB 2.5 compliant * update internal renderer usage * remove unused outstream_function logic * bugfix outstream options for default outstream renderer configuration * [PREB-10] fix empty title not configurable * add pbjs version * testing with outstream 5.3.0 * pbjs version into content.ext * made visibilityThreshold configurable * adjust position of pbjs version * Merge branch 'master' of https://github.com/prebid/Prebid.js into HEAD * update smartclip outstream player version to support outstream 6 release along with necessary config changes * Add support for schain * vacuuming * update custom header x-openrtb-version to 2.5 * add support for sitekey to smartxBidAdapter Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini Co-authored-by: smartclip-adtech <65160328+smartclip-adtech@users.noreply.github.com> * Adloox RTD Module: fix breakage since 7.x release (#9383) getTargetingData resulted in a crash as 'auction' is no longer passed in and we needed access to the global ORTB2 targetings. Reworked to be a lot simplier, and removed the ATF viewability segment and use the results of intersectionRtdProvider if present * JW Player Video Module: trigger error when missing div id (#9407) * checks for divId and get state existence * adds test for missing divId * add gppConsent (#9415) * ssp added to meta.demandSource (#9409) * adds a safety check (#9420) * Alkimi Bid Adapter: using the floors convention (#9368) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using Co-authored-by: Alexander Bogdanov Co-authored-by: Kalidas Engaiahraj Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail * init new Appush adapter (#9346) * Prebid 7.32.0 release * Increment version to 7.33.0-pre * TargetVideo Bid Adapter: Updating margin rule (#9428) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * PBjs Core (Price Floors) : Support inverseBidAdjustment function (#9395) * support inverseBidAdjustment function * pass in bidRequest object to adjustments * dont do fake bids bobby duh * Criteo Bid Adapter : Bump Publisher Tag version (#9429) Update reference to version 133 (latest) * IX Bid Adapter: retrieve user/agent hints and fix tmax issue (#9394) * feat: passthrough gpp information when it is provided [PB-1395] * chore: passthrough using module [PB-1395] * IX Bid Adapter Changes: change mtype logic, useragent client hints, change tmax logic * remove fallback for tmax timeout Co-authored-by: Chris Corbo * PBjs Core (Promises): fix static method GreedyPromise.resolve not working with Angular + Zone.js (#9426) * fix: Webpack v5 complain about named export from JSON modules * Index Exchange Adapter: fix "Should not import the named export 'EVENTS'.'AUCTION_DEBUG' (imported as 'EVENTS') from default-exporting module (only default export is available soon)"" * fix: Uncaught TypeError: Cannot read properties of undefined (reading 'getSlotElementId') * fix: Uncaught TypeError: Cannot read properties of undefined (reading 'getSlotElementId') * fix #9422 * refactor: fix linting error Co-authored-by: Javier Marín * USP consent management: handle errors from CMPs that cannot deal with `registerDeletion` (#9434) * nexx360 Bid Adapter: aliases list update (#9439) * ssp added to meta.demandSource * aliases update * Update live-connect-js version (#9438) * update live-connect-js * fix * fix package-lock.json * enable video/banner mediatypes for inImage/inBanner/inArticle/inScreen (#9417) * The payload extended with document.referer and canonicalUrl (#9416) * Prebid 7.33.0 release * Increment version to 7.34.0-pre * Admixer Bid Adapter : adding floor module support and new alias (#9427) * add floor module support * bidFloor update * Update admixerBidAdapter.md * Update admixerBidAdapter.js * remove tests * tests * floor test * Update admixerBidAdapter_spec.js * Update admixerBidAdapter_spec.js * Update admixerBidAdapter.js * https endpoint * lint bugs fix * Admatic Bid Adapter : bugfix with AdserverCurrency param (#9451) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * added support for user agent client hints (#9445) * nextMillenniumBidAdapter: fix replaceGetUserMacro function (#9442) * add video support * fix replaceUserMacro func * Add tests Co-authored-by: Mikhail Ivanchenko * kargo - adding support for vast url in bid response (#9447) * openxOrtbBidAdapter: fix device.sua test (#9452) * Criteo Bid Adapter : Bump Publisher Tag version (#9450) Co-authored-by: v.raybaud * BLIINK Bid Adapter: fix ttl (#9443) * fix(bliink): bid ttl * fix(bliink): ttl unit tests Co-authored-by: Samous * Bump ua-parser-js from 0.7.32 to 0.7.33 (#9456) Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.32 to 0.7.33. - [Release notes](https://github.com/faisalman/ua-parser-js/releases) - [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md) - [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.32...0.7.33) --- updated-dependencies: - dependency-name: ua-parser-js dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Triplelift Bid Adapter: Support for GPP in bid requests (#9455) * prioritize topmostlocation * adds test for topmostlocation / referrer * cleanup * delete param after test * TL-32803: Update referrer logic * TL-32803: Update referrer logic * TL-34204: Add support for GPP Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> * Lotame Panorama ID Module : add safari handling (#9418) * GRUE-176 work in progress, trying to make the id URL dynamic. * GRUE-176 After some attempts with leveraging environment variables, decided to look for an environment variable passed on configuration. * GRUE-177 WIP added a fix for handling timestamps, plus there's some temporary debugging that I was using to understand flow. * GRUE-177 Fixed bug with localStorage checking for empty, included null as a possible return value. * GRUE-177 updated the environment handling for dev, qa, and prod. I'm still on the fence on whether we need this, but it's allowing the tests to pass currently, so leaving it in for now. * GRUE-178 removed the dynamic URL handling for the ID endpoint. We will manage that change with the build process for testing. * GRUE-339 changes to check for browser, and accomodate Safari with a different URL. * GRUE-339 changes to check for browser, and accomodate Safari with a different URL. * GRUE-339 Removed the obfuscation from the Safari URL, as it was deemed unnecessary. * GRUE-339 corrected the safari id endpoint - I had forgotten that it was different than the usual one. * GRUE-339 Updated test to cover Safari handling. * GRUE-340 Updated the variable name for the cookieless domain, to remove the emphasis on Safari and better illustrate that this is a general approach. Co-authored-by: Mark Conrad Co-authored-by: Mark * Adnuntius Bid Adapter: Bug fix for multiple mime types. (#9458) * Holid bid adapter: skip user syncs when no bidders in bid response (#9462) * Seeding Alliance Bid Adapter: add banner support and get endpoint-url from config (#9404) * add seedingAlliance Adapter * add two native default params * ... * ... * seedingAlliance Adapter: add two more default native params * updating seedingAlliance Adapter * seedingAlliance Adapter * quickfix no bids + net revenue * bugfix replace auction price * change URL and add versioning * add vendorId to seedingAllianceAdapter * optimize code + banner support * add newline at the end of file * fix ci/circleci error * add new specs Co-authored-by: SeedingAllianceTech <55976067+SeedingAllianceTech@users.noreply.github.com> Co-authored-by: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Co-authored-by: Hendrick Musche * Emx Digital Bid Adapter : adding US Privacy string support (#9461) * adding ccpa support for emx_digital adapter * emx_digital ccpa compliance: lint fix * emx 3.0 compliance update * fix outstream renderer issue, update test spec * refactor formatVideoResponse function to use core-js/find * Add support for schain forwarding * Resolved issue with Schain object location * prebid 5.0 floor module and advertiserDomain support * liveramp idl and uid2.0 support for prebid * gpid support * remove utils ext * remove empty line * remove trailing spaces * move gpid test module * move gpid test module * removing trailing spaces from unit test * remove comments from unit test * Include us_privacy string in redirects (#8) * include us_privacy string in redirects * added test cases for us_privacy and gdpr * added test cases for gdpr without usp * updated test case when no privacy strings and fixed package-lock.json * revert package-lock.json Co-authored-by: EMXDigital * kick off ci tests Co-authored-by: Nick Colletti Co-authored-by: Nick Colletti Co-authored-by: Kiyoshi Hara Co-authored-by: Dan Bogdan Co-authored-by: Jherez Taylor Co-authored-by: EMXDigital Co-authored-by: Rakesh Balakrishnan Co-authored-by: Kevin Co-authored-by: Chris Huie * consumableBidAdapter: add gdpr and usp sync params (#9463) * PBS Bid Adapter : site should not exist when app is present (#9258) * Update prebidServerBidAdapter_spec.js * Update prebidServerBidAdapter_spec.js * fix test * remove app from site test * add site/app/dooh function * fix config * remove deepSetValue * add to ortb converter * add check * add back publisher.id * fix linting * ortb conversion lib: leave only one of dooh, app, or site in the request Co-authored-by: Demetrio Girardi * updated pbs filterSettings to sync with pbjs config filterSettings (#9423) * ArcSpan RTD Module: Initial Release (#9459) * Create arcspanRtdProvider.md * Added ArcSpan RTD Provider * Implemented alter bid request function in ArcSpan RTD Provider * Added unit tests for ArcSpan RTD Provider * Added more unit tests for ArcSpan RTD Provider * Load ArcSpan scripts using Prebid script loader * Fixed ArcSpan RTD module unit tests * Adding ArcSpan to submodules.json * Load ArcSpan script only if not already on the page * Load ArcSpan script only if not already on the page * Update issue tracker action to use new gh api (#9466) * Prebid 7.34.0 release * Increment version to 7.35.0-pre * PulsePoint Bid Adapter: support timeout/tmax (#9465) * ET-1691: Pulsepoint Analytics adapter for Prebid. (#1) * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: Adding pulsepoint analytics and tests for pulsepoint adapter * ET-1691: cleanup * ET-1691: minor * ET-1691: revert package.json change * Adding bidRequest to bidFactory.createBid method as per https://github.com/prebid/Prebid.js/issues/509 * ET-1765: Adding support for additional params in PulsePoint adapter (#2) * ET-1850: Fixing https://github.com/prebid/Prebid.js/issues/866 * Minor fix * Adding mandatory parameters to Bid * ET-12672 - passing tmax value to PulsePoint bidder * ET-12672 - using 500ms as a default and adding formatting Co-authored-by: anand-venkatraman * hadronId user id submodule: force localStorage (#9432) * Storing hadronId in localStorage after getting it from server * reverting hadronId documentation * Outbrain Bid Adapter: added video support (#9405) * add video support * add more video props * Smartadserver Bid Adapter: support floors per media type (#9437) * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Rework payloads enriching Co-authored-by: Meven Courouble * Smartytech Bid Adapter: Add video format (#9388) * Add new bid adapter for company smartytech * change domain to prod * update unit tests * remove unused code * remove unused code * add video type * update documentation * Core & priceFloors: pass bid request to `bidCpmAdjustment`; warn about invalid `adUnit.floors` definitions (#9441) * Core & priceFloors: pass `bidRequest` as third arg to `bidCpmAdjustment` * Floors: warn when adUnit.floors is not valid * Fixes potential error when reading _pssps localStorage key (#9474) * SmartyadsBidAdapter: update request params (#9472) Co-authored-by: Vasyl Rishko * Bump tibdex/github-app-token from 1.7.0 to 1.8.0 (#9479) Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/tibdex/github-app-token/releases) - [Commits](https://github.com/tibdex/github-app-token/compare/021a2405c7f990db57f5eae5397423dcc554159c...b62528385c34dbc9f38e5f4225ac829252d1ea92) --- updated-dependencies: - dependency-name: tibdex/github-app-token dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * C-Wire Bid Adapter: Code refactorings (#9446) * Reduce c-wire adapter to basic functionality (c-wire/creatives#3) * Minimize processing of bids * Add basic tests for adapter * Read cw_creative parameter from url and pass it as creativeId to the request (c-wire/creative#9) * Attach slot dimensions and maxWidth to request (c-wire/creatives#22) * Add feature flag support (c-wire/creatives#22) * Propagate cw_debug flag to ad server payload * Implement CWID (c-wire/prebid#3) * Add maxHeight CSS attribute (c-wire/creatives#22) * Update Prebid endpoint (c-wire/prebid#3) * Rename referrer to old property name * Map pageViewId to auctionId (c-wire/prebid#3) * Re-introduce pageId as required parameter (c-wire/prebid#3) * Map response body's bid.html property to bid.ad (c-wire/prebid#3) * Rename creativeId property to cwcreative in the payload (c-wire/prebid#3) * Flatten pageId and placementId into bid object (c-wire/prebid#3) * Send bid won and error events (c-wire/prebid#3) * Align cw* parameters with documentation and PBS adapter (c-wire/prebid#3) * QA Fix featureFlag check * Add refgroups from URL parameters (c-wire/prebid#3) * Inline adapter specific payload (c-wire/prebid#3) * Make pageViewId per prebid instance (c-wire/prebid#3) * Extract cwire extensions into own method (c-wire/prebid#3) * Update documentation (c-wire/prebid#3) * Add validations for placementId and pageId (c-wire/prebid#3) * QA Fix linting error * Add prebid version to payload (c-wire/prebid#3) * bidderCode fix (#9485) * Taboola Bid Adapter: pass nurl to bidResponse (#9482) * nurl-bugfix * nurl-bugfix * Vidazoo Bid Adapter: support for gpp consent and bid data (#9480) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * feat(module): pass gpp consent and bid data to server. * fix(module): change spec bidder timeout to 3000. --------- Co-authored-by: Udi Talias Co-authored-by: roman * Smartadserver Bid Adapter: support GPP consent (#9489) * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Add GPP support * Rework payloads enriching --------- Co-authored-by: Meven Courouble * Prebid Core: Added aliasRegistry to the Public API (#9467) * added aliasRegistry to the public api * reverted a few changes made for local dev * addressed feedback * AdYouLike Bid Adapter : add pbjs version information (#9476) * add required clickurl in every native adrequest * allows the native response to be given as is to prebid if possible * add unit tests on new Native case * Handle meta object in bid response with default addomains array * fix icon retrieval in Native case * Update priorities in case of multiple mediatypes given * improve robustness and fix associated unit test on picture urls * add support for params.size parameter * add unit test on new size format * Makes sure the playerSize format is consistent * enable Vast response on bidder adapter * fix lint errors * add test on Vast format case * add userId to bidrequest * revert package-lock.json changes * improve multiple mediatype handling * Expose adyoulike GVL id * fix icurl issue when retreiving icon for Native mediatype * update unit tests on icon url in native mediatype * target video endpoint when video mediatype is present * add unit test on video endpoint * detect if bid request has video * remove console log * Add size information in Video bid + unit tests * Remove unused method (old video retrieval) * update pagereferrer and pageUrl values * improve null robustness in native getAssetValue * change function body and add unit test * fix pageUrl in case not given i ortb2 * adjust pageUrl and referrer values * add unit tests on new priority behaviour * add pbjsversion in bid request * add unit test --------- Co-authored-by: GuillaumeA * Sonobi Bid Adapter: add additional sizes to bid request (#9413) * Added mediaTypes.video playerSize and sizes. * Save. * Order from least to most important. 1. Deprecated bid.size 2. bid.params.sizes. 3. mediaTypes.video.playerSize 4. mediaTypes.video.sizes 5. mediaTypes.banner.sizes * check for null. * Accepting multiple uniques sizes from different props. * Comments. * Added mediaTypes.video playerSize and sizes. * Save. * Order from least to most important. 1. Deprecated bid.size 2. bid.params.sizes. 3. mediaTypes.video.playerSize 4. mediaTypes.video.sizes 5. mediaTypes.banner.sizes * check for null. * Accepting multiple uniques sizes from different props. * Comments. * Circle CI error. Circle CI failing on line 298 due to trailing space, but there isnt one. * Circle CI error. Circle CI failing on line 298 due to trailing space, but there isnt one. * Readded hello_world.html --------- Co-authored-by: Zac Carlin * Add ESLint Plugin Recommendation for VSCODE (#9498) Just a quality of life improvement for any VSCode users. * Deprecate zeusPrimeRtdProvider submodule (#9358) Deprecates the zeusPrimeRtdProvider submodule and updates the associated documentation and tests. * Consent Management: Added config option for user action timeout (#9365) * progress * fixed tests and refactored * reverted some changes made while devloping on my local * in progress * updated action timeout logic * removed comment * reverted a few changes * reverted another change * addressed feedback * refactored actionTimeout logic * ZetaGlobalSsp bid adapter: bidfloor module (#9490) * ZetaGlobalSspBidAdapter: support bidfloors module * remove added space for linting * fix test --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Chris Huie * OneTag Bid Adapter: add gppConsent fetch (#9487) Co-authored-by: federico * Prebid 7.35.0 release * Increment version to 7.36.0-pre * Bump http-cache-semantics from 4.1.0 to 4.1.1 (#9502) Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/kornelski/http-cache-semantics/releases) - [Commits](https://github.com/kornelski/http-cache-semantics/commits) --- updated-dependencies: - dependency-name: http-cache-semantics dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix gpg Key Expiration for Debian Containers (#9497) Debian containers for yarn are having issues with key expirations. This change resolves that. Eventually the base images should be updated, but that timeline is unknown. There are a number of proposed solutions for the issue, but this one fixes ours. References to the issue: https://github.com/yarnpkg/yarn/issues/7866 Similar in AWS Builds: https://github.com/yarnpkg/yarn/issues/7866 * Topics Module: Mark Down file added (#9484) * Topics MD file added * Topics MD file changes * BrightcomSSP bid adapter: add new adapter (#9411) * BrightcomSSP: add new adapter * BrightcomSSP: add glvid * BrightcomSSP: add missing gvlid; update isBidRequestValidC * AIDEM Bidder Adapter: changed required params and win notice payload (#9457) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object * PlacementId is now a required parameter Added optional rateLimit parameter Added publisherId, siteId, placementId in win notice payload Added unit tests * Revert to optional placementId parameter Added missing semicolons --------- Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar * LiveIntent Id module: Update live-connect-js version (#9505) * Update live-connect-js version * fix eslint comment * Vidazoo Bid Adapter - webSessionId request param (#9504) * feat(module): multi size request * fix getUserSyncs added tests * update(module): package-lock.json from master * feat(module): VidazooBidAdapter - send top query params to server * Vidazoo Bid Adapter - added webSessionId to request --------- Co-authored-by: roman Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: Saar Amrani * pass referer to ortb request (#9475) Co-authored-by: Anthony Lin * Freewheel SSP Bid Adapter: bugfix for schain (#9492) * freewheel-sspBidAdapter: Bug Fix for schain (#9471) * Fixed schain logic to parse schain as string * Updated schain test to check schain string * Update freewheel-sspBidAdapter.js * kickoff tests --------- Co-authored-by: Scott Floam Co-authored-by: Patrick McCann Co-authored-by: Chris Huie * Add GVLID to smartx (#9512) * Scattered Bid Adapter: New Adapter (#9295) * Scattered module skeleton * Review fixes * Remove netRevenue check * Fix test * Review fixes * update amxIdSystem (#9508) * Scattered bid adapter: fix tests (#9514) * Neuwo RTD Module : initial release (#9385) * added neuwoRtdProvider.js (implementation), integration test spec, initial description document (neuwoRtdProvider.md) * neuwoRtdProvider finish: added _example.html using https://docs.prebid.org/dev-docs/examples/basic-example.html improved neuwoRtdProvider logging and error handling improved neuwoRtdProvider.md testing steps * neuwoRtdProvider.js -> updated to convert response using IAB to Tax ID conversion dictionary +tests, finished "no response" test * neuwoRtdProvider.js: added BILLABLE_EVENTs: 'auction' on getBidRequestData, 'request' on successful parse of neuwo request, 'general' on if global data actually extended using topics * review reflections: added injection into site.cattax, site.pagecat for "IAB 2.2", clarified conversion code conversion dictionary comment to include IAB 2+ - added tests to further ensure endpoint response malformation handling robustness * neuwoRtdProvider.js -> updated segtax code to compatible non-deprecated version (2.0 -> to 2.2, used conversion table is the same) * neuwoRtdProvider.js -> reduced: - amount and mutability of variables (const) - responsibility of injectTopics, callback lifted into callbacks of the ajax call - amount of billable events, on api call success only (expects marketing_categories) * SirData RTD Module: Change (add Seller Defined Audience (SDA) support) (#9448) * sirdataRtdProvider.js update (SDA support) Add support for Seller Defined Audience. Various improvement and support for new bidders. * update Sirdata RTD module linting issues fixed * New tests Add new tests to reach 80%+ test coverage * Update sirdataRtdProvider_spec.js * Update sirdataRtdProvider_spec.js * Nexx360 Bid Adapter: League-m alias added (#9518) * bidderCode fix * League-m added as alias * Feedad Bid Adapter: updated user-sync handling (#9519) * added file scaffold * added isBidRequestValid implementation * added local prototype of ad integration * added implementation for placement ID validation * fixed video context filter * applied lint to feedad bid adapter * added unit test for bid request validation * added buildRequest unit test * added unit tests for timeout and bid won callbacks * updated bid request to FeedAd API * added parsing of feedad api bid response * added transmisison of tracking events to FeedAd Api * code cleanup * updated feedad unit tests for buildRequest method * added unit tests for event tracking implementation * added unit test for interpretResponse method * added adapter documentation * added dedicated feedad example page * updated feedad adapter to use live system * updated FeedAd adapter placement ID regex * removed groups from FeedAd adapter placement ID regex * removed dedicated feedad example page * updated imports in FeedAd adapter file to use relative paths * updated FeedAd adapter unit test to use sinon.useFakeXMLHttpRequest() * added GDPR fields to the FeedAd bid request * removed video from supported media types of the FeedAd adapter * increased version code of FeedAd adapter to 1.0.2 * removed unnecessary check of bidder request * fixed unit test testing for old FeedAd version * removed video media type example from documentation file * added gvlid to FeedAd adapter * added decoration parameter to adapter documentation * added pass through of additional bid parameters * added user syncs to FeedAd bid adapter * increased FeedAd bid adapter version * lint pass over FeedAd bid adapter * fixed parsing of user syncs from server response * increased FeedAd bid adapter version * fixed version code in test file * added adapter and prebid version to bid request parameters * removed TODO item * added missing test case for user syncs * increased adapter version to 1.0.5 * updated from upstream * updated from upstream * updated user sync to accept multiple server responses * increased FeedAd bid adapter version code * fixed test case * fixed lint errors * Adding support of eids for smilewanted (#9440) * IX Bid Adapter - Add support for IMUID (#9500) * feat: add imuid to pbjs adapter [PB-1434] * chore: add unit test [PB-1434] --------- Co-authored-by: Chris Corbo * Admaru Bid Adapter: Add user sync (#9444) * AdmaruBidAdapter: add user sync * AdmaruBidAdapter: Use https in user sync * Limelight Digital Bid Adapter: added new custom fields for targeting (#9436) * Added new custom fields (#2) * Fixed integration * Fixes after review * Fixes after review --------- Co-authored-by: RuzannaAvetisyan <44729750+RuzannaAvetisyan@users.noreply.github.com> Co-authored-by: apykhteyev * Appnexus Bid Adapter : support alternate format for bid.params properties (#9503) * appnexus bid adapter - support alternate format for params * fix lint error * appnexus bid adapter - userid support update (#9507) * IX Bid Adapter: refactor buildRequest method (#9495) * feat: remove request splitting logic [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] * feat: remove splitting logic behind ft [PB-1389] --------- Co-authored-by: shahin.rahbariasl * bug fix for grid adapter not applying jw segment data when bidderRequest ortb2.user data doe not exist (#9521) * add sua support (#9523) * Read and pass device.sua object to translator payload (#9526) Co-authored-by: Kapil Tuptewar * Prebid 7.36.0 release * Increment version to 7.37.0-pre * Sonobi Bid Adapter: remove userid query param (#9496) * Removed the userid param This was causing a 414 error when userid and eids was duplicated * Update Sonobi Unit test for userid param * Remove deepClone import * Restored a userid unit test to ensure that the buildRequests function still works even if the publisher specifies a userid * Reworded the userid unit test and asserted that userid is not being set * Fixed undefined check in unit test * Update adapter docs (c-wire/prebid#3) (#9528) * MinuteMediaPlus Bid Adapter: New Bid Adapter (#9430) * MinuteMediaPlus bid adapter * bidder code changed to * incrx Bid Adapter : add support for adtype and settings (#9477) * Update incrxBidAdapter.js We have added new keys in the endpoint response (Line 71, 72) due to which we need to update our Adapter with the latest file * fix linting * Update incrxBidAdapter_spec.js We have added new keys in the response * Update incrxBidAdapter.js removed consolelog lines --------- Co-authored-by: Chris Huie * KueezRtbBidAdapter - pass gpp and bid data to server. (#9491) * Criteo Bid Adapter: Map full user & site objects (#9534) DPP-4310 * Update: Replaced adUnitCount with adUnits array (#9510) * Livewrapped Bid Adapter: added support for Price Floors Module (#9540) * Added support for the Price Floors Module * Use the ad server's currency when getting prices from the floors module * Default to USD if the ad server's currency can't be retrieved * Set the default currency at the right place * Added tests and made a minor change in how device width and height are calculated * Only include flrCur when ad requests contain floors * Proxistore Bid Adapter: migrate to new subdomain (#9537) * Migrate to new subdomain * Upgrade domain in tests as well * Mediago / Discovery Bid Adapters : update reporting of eids to server (#9539) * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter * Discovery Bid Adapter : parameter updates * Mediago Bid Adapter : parameter updates * Mediago Bid Adapter : code style format * rerun circleci * rerun circleci * rerun circleci * rerun circleci * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server --------- Co-authored-by: BaronYu * Core & PBS adapter: introduce bidder-level `ortb2Imp`; s2s-only `module` bids; PBS bidder-level `imp` params (#9470) * adUnit.bid.ortb2Imp support * Add "module" bids and PBS bidder-level imp params * Merge branch 'master' into bid-ortb2Imp * Update tests * nextMillennium Bid Adapter: cookie sync URL (#9522) * if no response, use hardcoded URL * lint added a space * net rev true * add test and fix queries (&) * Adagio Bid Adapter: update video params validation (#9542) * Adagio Bid Adapter: update video params validation * Adagio: update adapter doc * Triplelift Bid Adapter: set networkId in response (#9545) * prioritize topmostlocation * adds test for topmostlocation / referrer * cleanup * delete param after test * TL-32803: Update referrer logic * TL-32803: Update referrer logic * TL-34204: Add support for GPP * TL-34944: Add logic to pass networkId back in the bid response * TL-34944: Add logic to pass networkId back in the bid response * TL-34944: Add logic to pass networkId back in the bid response * few more tests --------- Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> * Oxxion Analytics Adapter : initial adapter release (#9449) * oxxion Analytics Adapter * debug(oxxionRtdProvider): onAuctionInit() * Revert "debug(oxxionRtdProvider): onAuctionInit()" This reverts commit d0894e34119fdbc9a075e35ea3f309774ca6bbbd. --------- Co-authored-by: Anthony Guyot * Revert "Oxxion Analytics Adapter : initial adapter release (#9449)" (#9549) This reverts commit 23fe08397668f252c774213a3be14617264a4e8d. * Adding tmax to bid request. (#9548) Defaulting to 400 if none is supplied. * Criteo Bid Adapter: Bumping PublisherTag version & Adapter version (#9554) * Prebid 7.37.0 release --------- Signed-off-by: dependabot[bot] Co-authored-by: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Co-authored-by: pm-azhar-mulla Co-authored-by: Gino <53079123+Skylinar@users.noreply.github.com> Co-authored-by: smartclip AdTechnology Co-authored-by: Gino Cirlini Co-authored-by: smartclip-adtech <65160328+smartclip-adtech@users.noreply.github.com> Co-authored-by: mmoschovas <63253416+mmoschovas@users.noreply.github.com> Co-authored-by: BaronJHYu <254878848@qq.com> Co-authored-by: BaronYu Co-authored-by: mcourouble <117740024+mcourouble@users.noreply.github.com> Co-authored-by: Krzysztof Sokół <88041828+smart-adserver@users.noreply.github.com> Co-authored-by: Udi Talias ⚛️ Co-authored-by: roman Co-authored-by: Saar Amrani <89377180+saar120@users.noreply.github.com> Co-authored-by: Saar Amrani Co-authored-by: Vladimir Co-authored-by: SebRobert Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Co-authored-by: Jordi Arnau Co-authored-by: ruben_tappx Co-authored-by: Prebid.js automated release Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tobias von Klipstein Co-authored-by: nkloeber <100145701+nkloeber@users.noreply.github.com> Co-authored-by: rahulgravito <105201950+rahulgravito@users.noreply.github.com> Co-authored-by: Fatih Kaya Co-authored-by: Omer Dotan <54346241+omerBrowsi@users.noreply.github.com> Co-authored-by: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Co-authored-by: Leandro Otani Co-authored-by: rtbh-lotani <83652735+rtbh-lotani@users.noreply.github.com> Co-authored-by: Tomasz Swirski Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> Co-authored-by: Léonard Labat Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: Demetrio Girardi Co-authored-by: Gabriel Chicoye Co-authored-by: fndigrazia Co-authored-by: AdmixerTech <35560933+AdmixerTech@users.noreply.github.com> Co-authored-by: Alexander Pykhteyev Co-authored-by: apykhteyev Co-authored-by: EngineeringProjectLimeLight <45598299+EngineeringProjectLimeLight@users.noreply.github.com> Co-authored-by: southern-growthcode <79725079+southern-growthcode@users.noreply.github.com> Co-authored-by: yieldlift <61510052+yieldlift@users.noreply.github.com> Co-authored-by: Danijel Predarski Co-authored-by: Karim Mourra Co-authored-by: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar Co-authored-by: Sławomir Kokłowski <38455696+skoklowski@users.noreply.github.com> Co-authored-by: skoklowski Co-authored-by: Jason Quaccia Co-authored-by: Elad Yosifon Co-authored-by: Saveliev Taras Co-authored-by: Taras Saveliev Co-authored-by: Yoko OYAMA Co-authored-by: Chris Huie Co-authored-by: ssorleti <100858633+ssorleti@users.noreply.github.com> Co-authored-by: Yohan Boutin Co-authored-by: GeoEdge-r-and-d <72186958+GeoEdge-r-and-d@users.noreply.github.com> Co-authored-by: daniel manan Co-authored-by: Patrick McCann Co-authored-by: JulieLorin Co-authored-by: Thomas Co-authored-by: Thomas De Stefano Co-authored-by: kkho339 <119444562+kkho339@users.noreply.github.com> Co-authored-by: redtram-tech <84892722+redtram-tech@users.noreply.github.com> Co-authored-by: Oleh Naimushyn Co-authored-by: Patrick Loughrey Co-authored-by: Nick Llerandi Co-authored-by: nllerandi3lift <75995508+nllerandi3lift@users.noreply.github.com> Co-authored-by: André Ricardo Gielow Co-authored-by: Andre Gielow Co-authored-by: Jeremy Sadwith Co-authored-by: Marcin Wrobel Co-authored-by: Wiem Zine El Abidine Co-authored-by: Vic R <103455651+victorlassomarketing@users.noreply.github.com> Co-authored-by: Damyan Co-authored-by: NikolayMGeorgiev Co-authored-by: Ventsislav Saraminev Co-authored-by: Dimitar Kalenderov Co-authored-by: Carlos Felix Co-authored-by: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Co-authored-by: Bill Newman Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Mykhailo Yaremchuk Co-authored-by: pm-priyanka-deshmane Co-authored-by: Malkov Mikhail Co-authored-by: Mikael Lundin Co-authored-by: Zeeshan Rasool Co-authored-by: Paulius Imbrasas Co-authored-by: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Co-authored-by: Olivier Co-authored-by: Steffen Anders Co-authored-by: Mikhail Ivanchenko Co-authored-by: Lionell Pack Co-authored-by: Lionell Pack Co-authored-by: GlobalsunHB <119595168+GlobalsunHB@users.noreply.github.com> Co-authored-by: Nitin Nimbalkar <96475150+nitin0610@users.noreply.github.com> Co-authored-by: jsfledd Co-authored-by: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Co-authored-by: federico Co-authored-by: geoffray-viously <95097046+geoffray-viously@users.noreply.github.com> Co-authored-by: mjaworskiccx <50406214+mjaworskiccx@users.noreply.github.com> Co-authored-by: Demetrio Girardi Co-authored-by: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Co-authored-by: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Co-authored-by: couchcrew-thomas Co-authored-by: vishal-dw <109065778+vishal-dw@users.noreply.github.com> Co-authored-by: Denis Logachov Co-authored-by: denys-berzoy-confiant <50574764+denys-berzoy-confiant@users.noreply.github.com> Co-authored-by: inna Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: Adserver.Online <61009237+adserver-online@users.noreply.github.com> Co-authored-by: dev Co-authored-by: AndreaC <67786179+darkstarac@users.noreply.github.com> Co-authored-by: Christoph Kipping <29540638+kippsterr@users.noreply.github.com> Co-authored-by: Krzysztof Desput Co-authored-by: Torsten Co-authored-by: radubarbos Co-authored-by: dumitrubarbos Co-authored-by: VitalyOrbit Co-authored-by: Dmitriy Shimko Co-authored-by: Хатламаджиян Виталий Co-authored-by: Nayan Savla Co-authored-by: Alexander Clouter Co-authored-by: dalmenarDevST <116064809+dalmenarDevST@users.noreply.github.com> Co-authored-by: Zeeshan Rasool Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Kalidas Engaiahraj Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail Co-authored-by: nitzanappush <116559356+nitzanappush@users.noreply.github.com> Co-authored-by: Dejan Grbavcic Co-authored-by: Robert Ray Martinez III Co-authored-by: Tachfine Co-authored-by: ccorbo Co-authored-by: Chris Corbo Co-authored-by: Javier Marín Co-authored-by: Javier Marín Co-authored-by: pkwisniowski-id5 <121963897+pkwisniowski-id5@users.noreply.github.com> Co-authored-by: Daria Boyko Co-authored-by: Jarosław Stropa <51368253+jaropenx@users.noreply.github.com> Co-authored-by: Mikhail Ivanchenko Co-authored-by: Andy Rusiecki Co-authored-by: Vincent Co-authored-by: v.raybaud Co-authored-by: samous Co-authored-by: Samous Co-authored-by: Mike Marcus Co-authored-by: Mark Conrad Co-authored-by: Mark Co-authored-by: Krzysztof Desput Co-authored-by: sag-jonhil <78849369+sag-jonhil@users.noreply.github.com> Co-authored-by: SeedingAllianceTech <55976067+SeedingAllianceTech@users.noreply.github.com> Co-authored-by: Hendrick Musche <107099114+sag-henmus@users.noreply.github.com> Co-authored-by: Hendrick Musche Co-authored-by: EMX Digital <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: Nick Colletti Co-authored-by: Nick Colletti Co-authored-by: Kiyoshi Hara Co-authored-by: Dan Bogdan Co-authored-by: Jherez Taylor Co-authored-by: EMXDigital Co-authored-by: Rakesh Balakrishnan Co-authored-by: Kevin Co-authored-by: Jason Piros Co-authored-by: Jose Cabal-Ugaz <6942011+josecu@users.noreply.github.com> Co-authored-by: Eugene Rachitskiy Co-authored-by: anand-venkatraman Co-authored-by: joseluis laso Co-authored-by: Mark Kuhar Co-authored-by: Meven Courouble Co-authored-by: preved-medved Co-authored-by: Paulius Imbrasas <880130+CremboC@users.noreply.github.com> Co-authored-by: rishko00 <43280707+rishko00@users.noreply.github.com> Co-authored-by: Vasyl Rishko Co-authored-by: Espen <2290914+espen-j@users.noreply.github.com> Co-authored-by: guiann Co-authored-by: GuillaumeA Co-authored-by: Zachary Carlin Co-authored-by: Zac Carlin Co-authored-by: Stephen Johnston Co-authored-by: Matt Crute <872334+mbcrute@users.noreply.github.com> Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Alexandru Co-authored-by: Maxim Schuwalow <16665913+mschuwalow@users.noreply.github.com> Co-authored-by: anthonyjl92 Co-authored-by: Anthony Lin Co-authored-by: Scott Floam Co-authored-by: Scott Floam Co-authored-by: Patrick McCann Co-authored-by: Muki Seiler Co-authored-by: Tomasz Kogut Co-authored-by: Nick Jacob Co-authored-by: moquity <33487117+moquity@users.noreply.github.com> Co-authored-by: nouchy <33549554+nouchy@users.noreply.github.com> Co-authored-by: Maxime DEYMÈS <47388595+MaxSmileWanted@users.noreply.github.com> Co-authored-by: RuzannaAvetisyan <44729750+RuzannaAvetisyan@users.noreply.github.com> Co-authored-by: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Co-authored-by: shahin.rahbariasl Co-authored-by: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Co-authored-by: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Co-authored-by: Kapil Tuptewar Co-authored-by: JonGoSonobi Co-authored-by: Prebid-Team Co-authored-by: pnh-pubx <73683023+pnh-pubx@users.noreply.github.com> Co-authored-by: msmeza Co-authored-by: Anthony Co-authored-by: JacobKlein26 <42449375+JacobKlein26@users.noreply.github.com> Co-authored-by: Gammelin Guillaume Co-authored-by: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Co-authored-by: Anthony Guyot --- .devcontainer/Dockerfile | 2 + .devcontainer/devcontainer.json | 3 +- .github/workflows/issue_tracker.yml | 43 +- integrationExamples/gpt/adloox.html | 5 + .../gpt/captifyRtdProvider_example.html | 167 + .../gpt/gpp_us_hello_world.html | 124 + .../gpt/gpp_us_hello_world_iframe.html | 12 + .../gpp_us_hello_world_iframe_subpage.html | 140 + integrationExamples/gpt/growthcode.html | 134 + integrationExamples/gpt/hello_world.html | 0 .../gpt/mgidRtdProvider_example.html | 143 + .../gpt/neuwoRtdProvider_example.html | 190 + .../gpt/permutiveRtdProvider_example.html | 3 +- integrationExamples/gpt/topics_frame.html | 43 + integrationExamples/gpt/userId_example.html | 12 +- .../gpt/weboramaRtdProvider_example.html | 61 +- .../videoModule/jwplayer/bidMarkedAsUsed.html | 96 + .../jwplayer/bidRequestScheduling.html | 89 + .../videoModule/jwplayer/eventListeners.html | 249 + .../jwplayer/gamAdServerMediation.html | 120 + .../videoModule/jwplayer/mediaMetadata.html | 81 + .../videoModule/jwplayer/playlist.html | 123 + .../videoModule/videojs/bidMarkedAsUsed.html | 123 + .../videojs/bidRequestScheduling.html | 132 + .../videoModule/videojs/eventListeners.html | 242 + .../videojs/gamAdServerMediation.html | 137 + .../videoModule/videojs/mediaMetadata.html | 103 + .../videoModule/videojs/playlist.html | 151 + karma.conf.maker.js | 2 +- .../analyticsAdapter/AnalyticsAdapter.js | 197 +- libraries/ortb2.5StrictTranslator/dsl.js | 54 + libraries/ortb2.5StrictTranslator/spec.js | 81 + .../ortb2.5StrictTranslator/translator.js | 37 + libraries/ortb2.5Translator/translator.js | 82 + libraries/ortbConverter/README.md | 378 + libraries/ortbConverter/converter.js | 135 + libraries/ortbConverter/lib/composer.js | 43 + .../ortbConverter/lib/mergeProcessors.js | 9 + libraries/ortbConverter/lib/sizes.js | 14 + libraries/ortbConverter/processors/banner.js | 40 + libraries/ortbConverter/processors/default.js | 153 + .../ortbConverter/processors/mediaType.js | 21 + libraries/ortbConverter/processors/native.js | 37 + libraries/ortbConverter/processors/video.js | 66 + libraries/pbsExtensions/pbsExtensions.js | 12 + .../pbsExtensions/processors/adUnitCode.js | 13 + libraries/pbsExtensions/processors/aliases.js | 17 + .../pbsExtensions/processors/mediaType.js | 23 + libraries/pbsExtensions/processors/params.js | 22 + libraries/pbsExtensions/processors/pbs.js | 104 + .../processors/requestExtPrebid.js | 30 + libraries/pbsExtensions/processors/video.js | 23 + libraries/video/constants/constants.js | 7 + libraries/video/constants/events.js | 98 + libraries/video/constants/ortb.js | 169 + libraries/video/constants/vendorCodes.js | 6 + libraries/video/shared/eventHandler.js | 18 + libraries/video/shared/helpers.js | 5 + libraries/video/shared/parentModule.js | 81 + libraries/video/shared/state.js | 47 + libraries/video/shared/vastXmlBuilder.js | 77 + libraries/video/shared/vastXmlEditor.js | 115 + modules/.submodules.json | 16 +- modules/1plusXRtdProvider.js | 37 +- modules/33acrossBidAdapter.js | 46 +- modules/33acrossIdSystem.js | 3 + modules/aaxBlockmeterRtdProvider.js | 59 + modules/aaxBlockmeterRtdProvider.md | 48 + modules/adagioBidAdapter.js | 107 +- modules/adagioBidAdapter.md | 30 +- modules/adhashBidAdapter.js | 145 +- modules/adhashBidAdapter.md | 4 +- modules/adkernelBidAdapter.js | 5 +- modules/adlooxAnalyticsAdapter.md | 8 +- modules/adlooxRtdProvider.js | 360 +- modules/adlooxRtdProvider.md | 12 +- modules/admaruBidAdapter.js | 20 +- modules/admaticBidAdapter.js | 206 + modules/admaticBidAdapter.md | 48 + modules/admixerBidAdapter.js | 30 +- modules/adnuntiusBidAdapter.js | 41 +- modules/aduptechBidAdapter.js | 90 +- modules/adyoulikeBidAdapter.js | 20 +- modules/aidemBidAdapter.js | 521 + modules/aidemBidAdapter.md | 191 + modules/ajaBidAdapter.js | 7 +- modules/alkimiBidAdapter.js | 29 +- modules/amxBidAdapter.js | 1 + modules/amxIdSystem.md | 12 +- modules/aolBidAdapter.js | 39 +- modules/appnexusBidAdapter.js | 182 +- modules/appushBidAdapter.js | 188 + modules/appushBidAdapter.md | 79 + modules/arcspanRtdProvider.js | 73 + modules/arcspanRtdProvider.md | 11 + modules/asoBidAdapter.js | 30 +- modules/asoBidAdapter.md | 11 +- modules/beopBidAdapter.js | 19 +- modules/bidViewability.js | 7 +- modules/bliinkBidAdapter.js | 2 +- modules/brightcomBidAdapter.js | 41 +- modules/brightcomSSPBidAdapter.js | 323 + modules/brightcomSSPBidAdapter.md | 46 + modules/browsiRtdProvider.js | 10 +- modules/bucksenseBidAdapter.js | 2 +- modules/captifyRtdProvider.js | 146 + modules/captifyRtdProvider.md | 68 + modules/categoryTranslation.js | 8 +- modules/ccxBidAdapter.js | 5 +- modules/chtnwBidAdapter.js | 110 + modules/chtnwBidAdapter.md | 31 + modules/colossussspBidAdapter.js | 10 +- modules/concertBidAdapter.js | 66 +- modules/confiantRtdProvider.js | 128 + modules/confiantRtdProvider.md | 45 + modules/connectIdSystem.js | 68 +- modules/connectIdSystem.md | 5 +- modules/consentManagement.js | 75 +- modules/consentManagementGpp.js | 386 + modules/consentManagementUsp.js | 40 +- modules/consumableBidAdapter.js | 22 +- modules/conversantAnalyticsAdapter.md | 25 +- modules/conversantBidAdapter.md | 6 +- modules/criteoBidAdapter.js | 37 +- modules/currency.js | 31 +- modules/cwireBidAdapter.js | 411 +- modules/cwireBidAdapter.md | 39 +- modules/dacIdSystem.js | 172 +- modules/dacIdSystem.md | 9 +- modules/datawrkzBidAdapter.js | 28 +- modules/dchain.js | 4 +- modules/debugging/legacy.js | 4 +- modules/dfpAdServerVideo.js | 7 +- modules/dgkeywordRtdProvider.js | 6 +- modules/discoveryBidAdapter.js | 82 +- modules/discoveryBidAdapter.md | 13 +- modules/emx_digitalBidAdapter.js | 11 +- modules/enrichmentFpdModule.js | 192 +- modules/eplanningBidAdapter.js | 46 +- modules/feedadBidAdapter.js | 54 +- modules/fledgeForGpt.md | 124 +- modules/fluctBidAdapter.js | 5 + modules/fpdModule/index.md | 7 +- modules/freewheel-sspBidAdapter.js | 61 +- modules/freewheel-sspBidAdapter.md | 2 +- modules/ftrackIdSystem.js | 37 +- modules/gdprEnforcement.js | 50 +- modules/genericAnalyticsAdapter.js | 157 + modules/geoedgeRtdProvider.js | 22 + modules/globalsunBidAdapter.js | 212 + modules/globalsunBidAdapter.md | 79 + modules/glomexBidAdapter.js | 2 + modules/gravitoIdSystem.js | 7 +- modules/gridBidAdapter.js | 472 +- modules/gridNMBidAdapter.js | 26 +- modules/growthCodeAnalyticsAdapter.js | 176 + modules/growthCodeAnalyticsAdapter.md | 41 + modules/growthCodeIdSystem.js | 14 +- modules/gumgumBidAdapter.js | 4 + modules/hadronIdSystem.js | 69 +- modules/holidBidAdapter.js | 175 + modules/holidBidAdapter.md | 36 + modules/iasRtdProvider.js | 27 +- modules/iasRtdProvider.md | 2 +- modules/id5IdSystem.js | 4 +- modules/impactifyBidAdapter.js | 20 +- modules/improvedigitalBidAdapter.js | 661 +- modules/incrxBidAdapter.js | 2 + modules/ixBidAdapter.js | 580 +- modules/jixieBidAdapter.js | 2 + modules/jwplayerRtdProvider.js | 3 +- modules/jwplayerVideoProvider.js | 936 + modules/jwplayerVideoProvider.md | 25 + modules/kargoBidAdapter.js | 30 +- modules/kueezRtbBidAdapter.js | 325 + modules/kueezRtbBidAdapter.md | 35 + modules/lassoBidAdapter.js | 16 +- modules/limelightDigitalBidAdapter.js | 9 +- modules/limelightDigitalBidAdapter.md | 14 +- modules/liveIntentIdSystem.js | 5 +- modules/livewrappedBidAdapter.js | 44 +- modules/lotamePanoramaIdSystem.js | 24 +- modules/magniteAnalyticsAdapter.js | 915 + modules/magniteAnalyticsAdapter.md | 18 + modules/mass.js | 4 +- modules/mediagoBidAdapter.js | 42 +- modules/medianetAnalyticsAdapter.js | 48 +- modules/medianetBidAdapter.js | 6 +- modules/medianetRtdProvider.js | 8 +- modules/mediasquareBidAdapter.js | 45 +- modules/mgidBidAdapter.js | 9 +- modules/mgidRtdProvider.js | 190 + modules/mgidRtdProvider.md | 51 + modules/microadBidAdapter.js | 33 +- modules/minutemediaBidAdapter.md | 6 +- modules/minutemediaplusBidAdapter.js | 291 + modules/minutemediaplusBidAdapter.md | 35 + modules/multibid/index.js | 22 +- modules/nativoBidAdapter.js | 56 +- modules/neuwoRtdProvider.js | 171 + modules/neuwoRtdProvider.md | 40 + modules/nextMillenniumBidAdapter.js | 196 +- modules/nexx360BidAdapter.js | 400 +- modules/oguryBidAdapter.js | 13 +- modules/onetagBidAdapter.js | 62 +- modules/openxBidAdapter.js | 5 + modules/openxBidAdapter.md | 29 +- modules/openxOrtbBidAdapter.js | 395 +- modules/orbitsoftBidAdapter.js | 150 + modules/outbrainBidAdapter.js | 109 +- modules/oxxionRtdProvider.js | 119 + modules/oxxionRtdProvider.md | 48 + modules/permutiveRtdProvider.js | 87 +- modules/permutiveRtdProvider.md | 37 +- modules/pixfutureBidAdapter.js | 61 +- modules/prebidServerBidAdapter/index.js | 791 +- .../prebidServerBidAdapter/ortbConverter.js | 281 + modules/priceFloors.js | 127 +- modules/proxistoreBidAdapter.js | 4 +- modules/pubCommonId.js | 3 +- modules/pubProvidedIdSystem.js | 2 + modules/pubmaticAnalyticsAdapter.js | 43 +- modules/pubmaticBidAdapter.js | 86 +- modules/pubwiseBidAdapter.js | 26 +- modules/pubxaiAnalyticsAdapter.js | 4 +- modules/pulsepointBidAdapter.js | 19 +- modules/rasBidAdapter.js | 11 +- modules/rasBidAdapter.md | 31 +- modules/redtramBidAdapter.js | 155 + modules/redtramBidAdapter.md | 33 + modules/riseBidAdapter.js | 18 +- modules/riseBidAdapter.md | 8 +- modules/rtbhouseBidAdapter.js | 119 +- modules/rtdModule/index.js | 24 +- modules/rubiconAnalyticsAdapter.js | 23 +- modules/rubiconBidAdapter.js | 3 +- modules/scatteredBidAdapter.js | 72 + modules/scatteredBidAdapter.md | 36 + modules/schain.js | 25 +- modules/seedingAllianceBidAdapter.js | 245 +- modules/seedtagBidAdapter.js | 156 +- modules/seedtagBidAdapter.md | 60 + modules/sharedIdSystem.js | 4 +- modules/sirdataRtdProvider.js | 546 +- modules/smaatoBidAdapter.js | 59 +- modules/smaatoBidAdapter.md | 78 + modules/smartadserverBidAdapter.js | 75 +- modules/smartxBidAdapter.js | 27 +- modules/smartyadsBidAdapter.md | 9 + modules/smartytechBidAdapter.js | 135 + modules/smartytechBidAdapter.md | 55 + modules/smilewantedBidAdapter.js | 5 + modules/smnBidAdapter.md | 52 + modules/sonobiBidAdapter.js | 38 +- modules/spotxBidAdapter.js | 31 +- modules/synacormediaBidAdapter.js | 2 +- modules/taboolaBidAdapter.js | 117 +- modules/taboolaBidAdapter.md | 2 - modules/tappxBidAdapter.js | 27 +- modules/targetVideoBidAdapter.js | 2 +- modules/teadsBidAdapter.js | 34 +- modules/teadsIdSystem.js | 239 + modules/teadsIdSystem.md | 22 + modules/topicsFpdModule.js | 183 +- modules/topicsFpdModule.md | 62 + modules/tripleliftBidAdapter.js | 8 + modules/ttdBidAdapter.js | 19 +- modules/uid2IdSystem.js | 248 +- modules/uid2IdSystem.md | 34 +- modules/userId/eids.js | 21 +- modules/userId/index.js | 161 +- modules/vidazooBidAdapter.js | 133 +- modules/videoModule/adQueue.js | 63 + modules/videoModule/addingSubmodule.md | 528 + modules/videoModule/coreVideo.js | 240 + modules/videoModule/gamAdServerSubmodule.js | 27 + modules/videoModule/index.js | 251 + .../videoModule/videoImpressionVerifier.js | 206 + modules/videojsVideoProvider.js | 854 + modules/videojsVideoProvider.md | 17 + modules/viouslyBidAdapter.js | 209 + modules/viouslyBidAdapter.md | 35 + modules/viqeoBidAdapter.js | 180 + modules/viqeoBidAdapter.md | 56 + modules/vrtcalBidAdapter.js | 8 +- modules/weboramaRtdProvider.js | 1443 +- modules/weboramaRtdProvider.md | 32 + modules/yahoosspBidAdapter.js | 9 +- modules/yandexBidAdapter.js | 104 +- modules/yandexBidAdapter.md | 14 +- modules/yieldlabBidAdapter.js | 315 +- modules/yieldliftBidAdapter.js | 6 +- modules/yieldmoBidAdapter.js | 44 +- modules/yieldoneBidAdapter.js | 79 +- modules/yieldoneBidAdapter.md | 118 +- modules/zeta_global_sspBidAdapter.js | 19 + modules/zeusPrimeRtdProvider.js | 37 + modules/zeusPrimeRtdProvider.md | 63 + package-lock.json | 29765 +++------------- package.json | 26 +- src/adRendering.js | 2 +- src/adapterManager.js | 128 +- src/adapters/bidderFactory.js | 14 +- src/adloader.js | 5 +- src/auction.js | 259 +- src/consentHandler.js | 19 + src/constants.json | 11 +- src/events.js | 10 +- src/fpd/enrichment.js | 94 + src/fpd/rootDomain.js | 57 + src/native.js | 51 +- src/pbjsORTB.js | 35 + src/prebid.js | 280 +- src/refererDetection.js | 7 +- src/storageManager.js | 9 +- src/targeting.js | 57 +- src/utils.js | 23 +- src/utils/cpm.js | 17 + src/utils/currency.js | 16 + src/utils/promise.js | 8 + test/fixtures/fixtures.js | 3 +- test/helpers/analytics.js | 34 + test/helpers/fpd.js | 71 + test/helpers/hookSetup.js | 5 + test/helpers/refererDetectionHelper.js | 88 + test/mocks/analyticsStub.js | 4 +- test/mocks/timers.js | 84 + test/spec/AnalyticsAdapter_spec.js | 235 +- test/spec/auctionmanager_spec.js | 314 +- test/spec/fpd/enrichment_spec.js | 213 + test/spec/fpd/gdpr_spec.js | 47 + test/spec/fpd/rootDomain_spec.js | 61 + test/spec/fpd/usp_spec.js | 36 + test/spec/modules/1plusXRtdProvider_spec.js | 44 +- test/spec/modules/33acrossBidAdapter_spec.js | 114 +- test/spec/modules/33acrossIdSystem_spec.js | 5 +- test/spec/modules/ViouslyBidAdapter_spec.js | 378 + test/spec/modules/aaxBlockmeter_spec.js | 58 + .../modules/adWMGAnalyticsAdapter_spec.js | 18 +- .../modules/adagioAnalyticsAdapter_spec.js | 43 +- test/spec/modules/adagioBidAdapter_spec.js | 37 +- test/spec/modules/adhashBidAdapter_spec.js | 76 +- test/spec/modules/adlooxRtdProvider_spec.js | 148 +- test/spec/modules/admaruBidAdapter_spec.js | 48 + test/spec/modules/admaticBidAdapter_spec.js | 305 + test/spec/modules/admixerBidAdapter_spec.js | 200 +- test/spec/modules/adnuntiusBidAdapter_spec.js | 122 + test/spec/modules/aduptechBidAdapter_spec.js | 131 +- test/spec/modules/adyoulikeBidAdapter_spec.js | 5 +- test/spec/modules/aidemBidAdapter_spec.js | 739 + test/spec/modules/ajaBidAdapter_spec.js | 22 +- test/spec/modules/alkimiBidAdapter_spec.js | 18 +- test/spec/modules/aolBidAdapter_spec.js | 73 +- test/spec/modules/appnexusBidAdapter_spec.js | 219 +- test/spec/modules/appushBidAdapter_spec.js | 372 + test/spec/modules/arcspanRtdProvider_spec.js | 187 + test/spec/modules/asoBidAdapter_spec.js | 4 +- test/spec/modules/beopBidAdapter_spec.js | 52 + .../modules/bidwatchAnalyticsAdapter_spec.js | 2 - test/spec/modules/bliinkBidAdapter_spec.js | 14 +- test/spec/modules/brightcomBidAdapter_spec.js | 95 +- .../modules/brightcomSSPBidAdapter_spec.js | 411 + test/spec/modules/browsiRtdProvider_spec.js | 3 +- test/spec/modules/captifyRtdProvider_spec.js | 253 + test/spec/modules/chtnwBidAdapter_spec.js | 105 + .../modules/colossussspBidAdapter_spec.js | 95 +- .../modules/concertAnalyticsAdapter_spec.js | 6 +- test/spec/modules/concertBidAdapter_spec.js | 95 +- test/spec/modules/confiantRtdProvider_spec.js | 107 + test/spec/modules/connectIdSystem_spec.js | 218 +- .../spec/modules/consentManagementGpp_spec.js | 577 + .../spec/modules/consentManagementUsp_spec.js | 95 +- test/spec/modules/consentManagement_spec.js | 50 +- .../spec/modules/consumableBidAdapter_spec.js | 46 + test/spec/modules/criteoBidAdapter_spec.js | 53 +- test/spec/modules/currency_spec.js | 20 +- test/spec/modules/cwireBidAdapter_spec.js | 584 +- test/spec/modules/dacIdSystem_spec.js | 104 +- .../spec/modules/dgkeywordRtdProvider_spec.js | 6 +- test/spec/modules/discoveryBidAdapter_spec.js | 17 +- test/spec/modules/eids_spec.js | 29 +- .../modules/emx_digitalBidAdapter_spec.js | 31 +- test/spec/modules/enrichmentFpdModule_spec.js | 158 - .../modules/eplanningAnalyticsAdapter_spec.js | 3 - test/spec/modules/eplanningBidAdapter_spec.js | 286 +- test/spec/modules/feedadBidAdapter_spec.js | 135 +- test/spec/modules/fluctBidAdapter_spec.js | 38 +- test/spec/modules/fpdModule_spec.js | 288 +- .../modules/freewheel-sspBidAdapter_spec.js | 31 +- test/spec/modules/ftrackIdSystem_spec.js | 302 +- test/spec/modules/gdprEnforcement_spec.js | 183 +- .../modules/genericAnalyticsAdapter_spec.js | 284 + test/spec/modules/geoedgeRtdProvider_spec.js | 15 + test/spec/modules/globalsunBidAdapter_spec.js | 398 + test/spec/modules/glomexBidAdapter_spec.js | 4 + test/spec/modules/gravitoIdSystem_spec.js | 2 +- test/spec/modules/gridBidAdapter_spec.js | 308 +- .../growthCodeAnalyticsAdapter_spec.js | 70 + test/spec/modules/gumgumBidAdapter_spec.js | 15 + test/spec/modules/hadronIdSystem_spec.js | 10 +- test/spec/modules/holidBidAdapter_spec.js | 194 + test/spec/modules/iasRtdProvider_spec.js | 58 +- test/spec/modules/impactifyBidAdapter_spec.js | 13 + .../modules/improvedigitalBidAdapter_spec.js | 468 +- test/spec/modules/incrxBidAdapter_spec.js | 4 + .../modules/invisiblyAnalyticsAdapter_spec.js | 44 +- test/spec/modules/ixBidAdapter_spec.js | 889 +- test/spec/modules/jixieBidAdapter_spec.js | 5 + test/spec/modules/jwplayerRtdProvider_spec.js | 6 +- test/spec/modules/kargoBidAdapter_spec.js | 112 +- .../modules/konduitAnalyticsAdapter_spec.js | 2 - test/spec/modules/kueezRtbBidAdapter_spec.js | 560 + .../limelightDigitalBidAdapter_spec.js | 40 +- .../liveIntentAnalyticsAdapter_spec.js | 9 +- .../modules/livewrappedBidAdapter_spec.js | 269 +- .../modules/lotamePanoramaIdSystem_spec.js | 27 +- .../modules/magniteAnalyticsAdapter_spec.js | 2010 ++ .../modules/medianetAnalyticsAdapter_spec.js | 62 +- test/spec/modules/medianetBidAdapter_spec.js | 76 + test/spec/modules/medianetRtdProvider_spec.js | 6 +- .../modules/mediasquareBidAdapter_spec.js | 6 +- test/spec/modules/mgidBidAdapter_spec.js | 50 + test/spec/modules/mgidRtdProvider_spec.js | 366 + test/spec/modules/microadBidAdapter_spec.js | 48 + .../modules/minutemediaplusBidAdapter_spec.js | 519 + test/spec/modules/nativoBidAdapter_spec.js | 84 + test/spec/modules/neuwoRtdProvider_spec.js | 123 + .../modules/nextMillenniumBidAdapter_spec.js | 238 +- test/spec/modules/nexx360BidAdapter_spec.js | 658 +- test/spec/modules/oguryBidAdapter_spec.js | 76 +- test/spec/modules/onetagBidAdapter_spec.js | 95 +- test/spec/modules/openxBidAdapter_spec.js | 41 + test/spec/modules/openxOrtbBidAdapter_spec.js | 202 +- .../modules/optimonAnalyticsAdapter_spec.js | 14 +- test/spec/modules/orbitsoftBidAdapter_spec.js | 255 + test/spec/modules/outbrainBidAdapter_spec.js | 200 +- test/spec/modules/oxxionRtdProvider_spec.js | 142 + .../spec/modules/permutiveRtdProvider_spec.js | 180 +- .../modules/pianoDmpAnalyticsAdapter_spec.js | 15 +- .../modules/prebidServerBidAdapter_spec.js | 618 +- .../prebidmanagerAnalyticsAdapter_spec.js | 15 +- test/spec/modules/priceFloors_spec.js | 165 +- .../spec/modules/proxistoreBidAdapter_spec.js | 4 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 52 +- test/spec/modules/pubmaticBidAdapter_spec.js | 84 +- .../modules/pubperfAnalyticsAdapter_spec.js | 25 +- .../modules/pubstackAnalyticsAdapter_spec.js | 20 +- .../modules/pubwiseAnalyticsAdapter_spec.js | 32 +- test/spec/modules/pubwiseBidAdapter_spec.js | 10 +- .../modules/pubxaiAnalyticsAdapter_spec.js | 4 +- .../spec/modules/pulsepointBidAdapter_spec.js | 135 +- test/spec/modules/rasBidAdapter_spec.js | 6 +- test/spec/modules/realTimeDataModule_spec.js | 65 + test/spec/modules/redtramBidAdapter_spec.js | 256 + test/spec/modules/riseBidAdapter_spec.js | 31 + test/spec/modules/rtbhouseBidAdapter_spec.js | 79 + .../modules/rubiconAnalyticsAdapter_spec.js | 5 +- test/spec/modules/scatteredBidAdapter_spec.js | 210 + .../modules/seedingAllianceAdapter_spec.js | 89 +- test/spec/modules/seedtagBidAdapter_spec.js | 286 +- .../modules/sigmoidAnalyticsAdapter_spec.js | 12 +- test/spec/modules/sirdataRtdProvider_spec.js | 138 +- test/spec/modules/smaatoBidAdapter_spec.js | 343 +- .../modules/smartadserverBidAdapter_spec.js | 201 +- test/spec/modules/smartxBidAdapter_spec.js | 42 + .../spec/modules/smartytechBidAdapter_spec.js | 320 + .../modules/smilewantedBidAdapter_spec.js | 57 +- test/spec/modules/sonobiBidAdapter_spec.js | 31 +- .../modules/sovrnAnalyticsAdapter_spec.js | 24 +- test/spec/modules/taboolaBidAdapter_spec.js | 387 +- .../modules/targetVideoBidAdapter_spec.js | 4 +- test/spec/modules/teadsBidAdapter_spec.js | 125 +- test/spec/modules/teadsIdSystem_spec.js | 271 + test/spec/modules/topicsFpdModule_spec.js | 225 +- .../spec/modules/tripleliftBidAdapter_spec.js | 31 +- test/spec/modules/ttdBidAdapter_spec.js | 70 +- test/spec/modules/uid2IdSystem_spec.js | 310 + test/spec/modules/userId_spec.js | 173 +- test/spec/modules/vidazooBidAdapter_spec.js | 201 +- test/spec/modules/videoModule/adQueue_spec.js | 172 + .../modules/videoModule/coreVideo_spec.js | 98 + test/spec/modules/videoModule/pbVideo_spec.js | 374 + .../videoModule/shared/parentModule_spec.js | 72 + .../modules/videoModule/shared/state_spec.js | 26 + .../videoModule/shared/vastXmlBuilder_spec.js | 103 + .../videoModule/shared/vastXmlEditor_spec.js | 209 + .../submodules/jwplayerVideoProvider_spec.js | 881 + .../submodules/videojsVideoProvider_spec.js | 391 + .../videoImpressionVerifier_spec.js | 112 + test/spec/modules/viqeoBidAdapter_spec.js | 124 + test/spec/modules/vrtcalBidAdapter_spec.js | 13 +- test/spec/modules/weboramaRtdProvider_spec.js | 382 + test/spec/modules/yahoosspBidAdapter_spec.js | 62 +- test/spec/modules/yandexBidAdapter_spec.js | 35 +- test/spec/modules/yieldlabBidAdapter_spec.js | 736 +- test/spec/modules/yieldliftBidAdapter_spec.js | 2 +- test/spec/modules/yieldmoBidAdapter_spec.js | 77 +- .../modules/yieldoneAnalyticsAdapter_spec.js | 7 +- test/spec/modules/yieldoneBidAdapter_spec.js | 41 +- .../modules/zeta_global_sspBidAdapter_spec.js | 25 +- test/spec/native_spec.js | 124 +- test/spec/ortb2.5StrictTranslator/dsl_spec.js | 137 + .../spec/ortb2.5StrictTranslator/spec_spec.js | 358 + .../translator_spec.js | 16 + .../spec/ortb2.5Translator/translator_spec.js | 64 + test/spec/ortbConverter/banner_spec.js | 203 + test/spec/ortbConverter/composer_spec.js | 69 + test/spec/ortbConverter/converter_spec.js | 283 + test/spec/ortbConverter/currency_spec.js | 40 + .../ortbConverter/default_processors_spec.js | 23 + test/spec/ortbConverter/gdpr_spec.js | 9 + test/spec/ortbConverter/mediaTypes_spec.js | 67 + .../ortbConverter/mergeProcessors_spec.js | 59 + test/spec/ortbConverter/multibid_spec.js | 35 + test/spec/ortbConverter/native_spec.js | 95 + test/spec/ortbConverter/pbjsORTB_spec.js | 67 + .../pbsExtensions/adUnitCode_spec.js | 23 + .../pbsExtensions/aliases_spec.js | 57 + .../pbsExtensions/params_spec.js | 96 + .../ortbConverter/pbsExtensions/video_spec.js | 52 + test/spec/ortbConverter/priceFloors_spec.js | 143 + test/spec/ortbConverter/schain_spec.js | 33 + test/spec/ortbConverter/userId_spec.js | 21 + test/spec/ortbConverter/video_spec.js | 189 + test/spec/refererDetection_spec.js | 141 +- test/spec/unit/core/adapterManager_spec.js | 249 + test/spec/unit/core/bidderFactory_spec.js | 73 +- test/spec/unit/core/storageManager_spec.js | 3 +- test/spec/unit/core/targeting_spec.js | 71 +- test/spec/unit/pbjs_api_spec.js | 268 +- test/spec/unit/utils/cpm_spec.js | 64 + test/spec/utils_spec.js | 46 +- 532 files changed, 56150 insertions(+), 33870 deletions(-) create mode 100644 integrationExamples/gpt/captifyRtdProvider_example.html create mode 100644 integrationExamples/gpt/gpp_us_hello_world.html create mode 100644 integrationExamples/gpt/gpp_us_hello_world_iframe.html create mode 100644 integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html create mode 100644 integrationExamples/gpt/growthcode.html mode change 100755 => 100644 integrationExamples/gpt/hello_world.html create mode 100644 integrationExamples/gpt/mgidRtdProvider_example.html create mode 100644 integrationExamples/gpt/neuwoRtdProvider_example.html create mode 100644 integrationExamples/gpt/topics_frame.html create mode 100644 integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html create mode 100644 integrationExamples/videoModule/jwplayer/bidRequestScheduling.html create mode 100644 integrationExamples/videoModule/jwplayer/eventListeners.html create mode 100644 integrationExamples/videoModule/jwplayer/gamAdServerMediation.html create mode 100644 integrationExamples/videoModule/jwplayer/mediaMetadata.html create mode 100644 integrationExamples/videoModule/jwplayer/playlist.html create mode 100644 integrationExamples/videoModule/videojs/bidMarkedAsUsed.html create mode 100644 integrationExamples/videoModule/videojs/bidRequestScheduling.html create mode 100644 integrationExamples/videoModule/videojs/eventListeners.html create mode 100644 integrationExamples/videoModule/videojs/gamAdServerMediation.html create mode 100644 integrationExamples/videoModule/videojs/mediaMetadata.html create mode 100644 integrationExamples/videoModule/videojs/playlist.html create mode 100644 libraries/ortb2.5StrictTranslator/dsl.js create mode 100644 libraries/ortb2.5StrictTranslator/spec.js create mode 100644 libraries/ortb2.5StrictTranslator/translator.js create mode 100644 libraries/ortb2.5Translator/translator.js create mode 100644 libraries/ortbConverter/README.md create mode 100644 libraries/ortbConverter/converter.js create mode 100644 libraries/ortbConverter/lib/composer.js create mode 100644 libraries/ortbConverter/lib/mergeProcessors.js create mode 100644 libraries/ortbConverter/lib/sizes.js create mode 100644 libraries/ortbConverter/processors/banner.js create mode 100644 libraries/ortbConverter/processors/default.js create mode 100644 libraries/ortbConverter/processors/mediaType.js create mode 100644 libraries/ortbConverter/processors/native.js create mode 100644 libraries/ortbConverter/processors/video.js create mode 100644 libraries/pbsExtensions/pbsExtensions.js create mode 100644 libraries/pbsExtensions/processors/adUnitCode.js create mode 100644 libraries/pbsExtensions/processors/aliases.js create mode 100644 libraries/pbsExtensions/processors/mediaType.js create mode 100644 libraries/pbsExtensions/processors/params.js create mode 100644 libraries/pbsExtensions/processors/pbs.js create mode 100644 libraries/pbsExtensions/processors/requestExtPrebid.js create mode 100644 libraries/pbsExtensions/processors/video.js create mode 100644 libraries/video/constants/constants.js create mode 100644 libraries/video/constants/events.js create mode 100644 libraries/video/constants/ortb.js create mode 100644 libraries/video/constants/vendorCodes.js create mode 100644 libraries/video/shared/eventHandler.js create mode 100644 libraries/video/shared/helpers.js create mode 100644 libraries/video/shared/parentModule.js create mode 100644 libraries/video/shared/state.js create mode 100644 libraries/video/shared/vastXmlBuilder.js create mode 100644 libraries/video/shared/vastXmlEditor.js create mode 100644 modules/aaxBlockmeterRtdProvider.js create mode 100644 modules/aaxBlockmeterRtdProvider.md create mode 100644 modules/admaticBidAdapter.js create mode 100644 modules/admaticBidAdapter.md create mode 100644 modules/aidemBidAdapter.js create mode 100644 modules/aidemBidAdapter.md create mode 100644 modules/appushBidAdapter.js create mode 100644 modules/appushBidAdapter.md create mode 100644 modules/arcspanRtdProvider.js create mode 100644 modules/arcspanRtdProvider.md create mode 100644 modules/brightcomSSPBidAdapter.js create mode 100644 modules/brightcomSSPBidAdapter.md create mode 100644 modules/captifyRtdProvider.js create mode 100644 modules/captifyRtdProvider.md create mode 100644 modules/chtnwBidAdapter.js create mode 100644 modules/chtnwBidAdapter.md create mode 100644 modules/confiantRtdProvider.js create mode 100644 modules/confiantRtdProvider.md create mode 100644 modules/consentManagementGpp.js create mode 100644 modules/genericAnalyticsAdapter.js create mode 100644 modules/globalsunBidAdapter.js create mode 100644 modules/globalsunBidAdapter.md create mode 100644 modules/growthCodeAnalyticsAdapter.js create mode 100644 modules/growthCodeAnalyticsAdapter.md create mode 100644 modules/holidBidAdapter.js create mode 100644 modules/holidBidAdapter.md create mode 100644 modules/jwplayerVideoProvider.js create mode 100644 modules/jwplayerVideoProvider.md create mode 100644 modules/kueezRtbBidAdapter.js create mode 100644 modules/kueezRtbBidAdapter.md create mode 100644 modules/magniteAnalyticsAdapter.js create mode 100644 modules/magniteAnalyticsAdapter.md create mode 100644 modules/mgidRtdProvider.js create mode 100644 modules/mgidRtdProvider.md create mode 100644 modules/minutemediaplusBidAdapter.js create mode 100644 modules/minutemediaplusBidAdapter.md create mode 100644 modules/neuwoRtdProvider.js create mode 100644 modules/neuwoRtdProvider.md create mode 100644 modules/orbitsoftBidAdapter.js create mode 100644 modules/oxxionRtdProvider.js create mode 100644 modules/oxxionRtdProvider.md create mode 100644 modules/prebidServerBidAdapter/ortbConverter.js create mode 100644 modules/redtramBidAdapter.js create mode 100644 modules/redtramBidAdapter.md create mode 100644 modules/scatteredBidAdapter.js create mode 100644 modules/scatteredBidAdapter.md create mode 100644 modules/smartytechBidAdapter.js create mode 100644 modules/smartytechBidAdapter.md create mode 100644 modules/smnBidAdapter.md create mode 100644 modules/teadsIdSystem.js create mode 100644 modules/teadsIdSystem.md create mode 100644 modules/topicsFpdModule.md create mode 100644 modules/videoModule/adQueue.js create mode 100644 modules/videoModule/addingSubmodule.md create mode 100644 modules/videoModule/coreVideo.js create mode 100644 modules/videoModule/gamAdServerSubmodule.js create mode 100644 modules/videoModule/index.js create mode 100644 modules/videoModule/videoImpressionVerifier.js create mode 100644 modules/videojsVideoProvider.js create mode 100644 modules/videojsVideoProvider.md create mode 100644 modules/viouslyBidAdapter.js create mode 100644 modules/viouslyBidAdapter.md create mode 100644 modules/viqeoBidAdapter.js create mode 100644 modules/viqeoBidAdapter.md create mode 100644 modules/zeusPrimeRtdProvider.js create mode 100644 modules/zeusPrimeRtdProvider.md create mode 100644 src/fpd/enrichment.js create mode 100644 src/fpd/rootDomain.js create mode 100644 src/pbjsORTB.js create mode 100644 src/utils/cpm.js create mode 100644 src/utils/currency.js create mode 100644 test/helpers/analytics.js create mode 100644 test/helpers/fpd.js create mode 100644 test/helpers/hookSetup.js create mode 100644 test/helpers/refererDetectionHelper.js create mode 100644 test/mocks/timers.js create mode 100644 test/spec/fpd/enrichment_spec.js create mode 100644 test/spec/fpd/gdpr_spec.js create mode 100644 test/spec/fpd/rootDomain_spec.js create mode 100644 test/spec/fpd/usp_spec.js create mode 100644 test/spec/modules/ViouslyBidAdapter_spec.js create mode 100644 test/spec/modules/aaxBlockmeter_spec.js create mode 100644 test/spec/modules/admaticBidAdapter_spec.js create mode 100644 test/spec/modules/aidemBidAdapter_spec.js create mode 100644 test/spec/modules/appushBidAdapter_spec.js create mode 100644 test/spec/modules/arcspanRtdProvider_spec.js create mode 100644 test/spec/modules/brightcomSSPBidAdapter_spec.js create mode 100644 test/spec/modules/captifyRtdProvider_spec.js create mode 100644 test/spec/modules/chtnwBidAdapter_spec.js create mode 100644 test/spec/modules/confiantRtdProvider_spec.js create mode 100644 test/spec/modules/consentManagementGpp_spec.js create mode 100644 test/spec/modules/genericAnalyticsAdapter_spec.js create mode 100644 test/spec/modules/globalsunBidAdapter_spec.js create mode 100644 test/spec/modules/growthCodeAnalyticsAdapter_spec.js create mode 100644 test/spec/modules/holidBidAdapter_spec.js create mode 100644 test/spec/modules/kueezRtbBidAdapter_spec.js create mode 100644 test/spec/modules/magniteAnalyticsAdapter_spec.js create mode 100644 test/spec/modules/mgidRtdProvider_spec.js create mode 100644 test/spec/modules/minutemediaplusBidAdapter_spec.js create mode 100644 test/spec/modules/neuwoRtdProvider_spec.js create mode 100644 test/spec/modules/orbitsoftBidAdapter_spec.js create mode 100644 test/spec/modules/oxxionRtdProvider_spec.js create mode 100644 test/spec/modules/redtramBidAdapter_spec.js create mode 100644 test/spec/modules/scatteredBidAdapter_spec.js create mode 100644 test/spec/modules/smartytechBidAdapter_spec.js create mode 100644 test/spec/modules/teadsIdSystem_spec.js create mode 100644 test/spec/modules/uid2IdSystem_spec.js create mode 100644 test/spec/modules/videoModule/adQueue_spec.js create mode 100644 test/spec/modules/videoModule/coreVideo_spec.js create mode 100644 test/spec/modules/videoModule/pbVideo_spec.js create mode 100644 test/spec/modules/videoModule/shared/parentModule_spec.js create mode 100644 test/spec/modules/videoModule/shared/state_spec.js create mode 100644 test/spec/modules/videoModule/shared/vastXmlBuilder_spec.js create mode 100644 test/spec/modules/videoModule/shared/vastXmlEditor_spec.js create mode 100644 test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js create mode 100644 test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js create mode 100644 test/spec/modules/videoModule/videoImpressionVerifier_spec.js create mode 100644 test/spec/modules/viqeoBidAdapter_spec.js create mode 100644 test/spec/ortb2.5StrictTranslator/dsl_spec.js create mode 100644 test/spec/ortb2.5StrictTranslator/spec_spec.js create mode 100644 test/spec/ortb2.5StrictTranslator/translator_spec.js create mode 100644 test/spec/ortb2.5Translator/translator_spec.js create mode 100644 test/spec/ortbConverter/banner_spec.js create mode 100644 test/spec/ortbConverter/composer_spec.js create mode 100644 test/spec/ortbConverter/converter_spec.js create mode 100644 test/spec/ortbConverter/currency_spec.js create mode 100644 test/spec/ortbConverter/default_processors_spec.js create mode 100644 test/spec/ortbConverter/gdpr_spec.js create mode 100644 test/spec/ortbConverter/mediaTypes_spec.js create mode 100644 test/spec/ortbConverter/mergeProcessors_spec.js create mode 100644 test/spec/ortbConverter/multibid_spec.js create mode 100644 test/spec/ortbConverter/native_spec.js create mode 100644 test/spec/ortbConverter/pbjsORTB_spec.js create mode 100644 test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js create mode 100644 test/spec/ortbConverter/pbsExtensions/aliases_spec.js create mode 100644 test/spec/ortbConverter/pbsExtensions/params_spec.js create mode 100644 test/spec/ortbConverter/pbsExtensions/video_spec.js create mode 100644 test/spec/ortbConverter/priceFloors_spec.js create mode 100644 test/spec/ortbConverter/schain_spec.js create mode 100644 test/spec/ortbConverter/userId_spec.js create mode 100644 test/spec/ortbConverter/video_spec.js create mode 100644 test/spec/unit/utils/cpm_spec.js diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cfb29ebdfa9..69e13850258 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,8 @@ ARG VARIANT="12" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg + # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0176b8317b3..104d9a38132 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,7 +16,8 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "nickdodd79.gulptasks" + "nickdodd79.gulptasks", + "dbaeumer.vscode-eslint" ], // 9999 is web server, 9876 is karma diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 05d08b2b0d7..69cf4c5fc7f 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@f717b5ecd4534d3c4df4ce9b5c1c2214f0f7cd06 + uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92 with: app_id: ${{ secrets.ISSUE_APP_ID }} private_key: ${{ secrets.ISSUE_APP_PEM }} @@ -29,21 +29,30 @@ jobs: gh api graphql -f query=' query($org: String!, $number: Int!) { organization(login: $org){ - projectNext(number: $number) { + projectV2(number: $number) { id fields(first:100) { nodes { - id - name - settings + ... on ProjectV2Field { + id + name + } + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } } } } } }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json - echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV - echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV + echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV + echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name=="'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV - name: Add issue to project env: @@ -52,9 +61,9 @@ jobs: run: | gh api graphql -f query=' mutation($project:ID!, $issue:ID!) { - addProjectNextItem(input: {projectId: $project, contentId: $issue}) { - projectNextItem { - id, + addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) { + item { + id content { ... on Issue { createdAt @@ -67,8 +76,8 @@ jobs: } }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json - echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV - echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_ID='$(jq '.data.addProjectV2ItemById.item.id' issue_data.json) >> $GITHUB_ENV + echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectV2ItemById.item.content.createdAt' issue_data.json | cut -c 2-11) >> $GITHUB_ENV - name: Set fields env: @@ -79,15 +88,17 @@ jobs: $project: ID! $item: ID! $date_field: ID! - $date_value: String! + $date_value: Date! ) { - set_creation_date: updateProjectNextItemField(input: { + set_creation_date: updateProjectV2ItemFieldValue(input: { projectId: $project itemId: $item fieldId: $date_field - value: $date_value + value: { + date: $date_value + } }) { - projectNextItem { + projectV2Item { id } } diff --git a/integrationExamples/gpt/adloox.html b/integrationExamples/gpt/adloox.html index fd61267479d..78fba71f774 100644 --- a/integrationExamples/gpt/adloox.html +++ b/integrationExamples/gpt/adloox.html @@ -147,8 +147,13 @@ realTimeData: { auctionDelay: AUCTION_DELAY, dataProviders: [ + { + name: 'intersection', + waitForIt: true + }, { name: 'adloox', + waitForIt: true, params: { // optional, defaults shown thresholds: [ 50, 60, 70, 80, 90 ], slotinpath: false diff --git a/integrationExamples/gpt/captifyRtdProvider_example.html b/integrationExamples/gpt/captifyRtdProvider_example.html new file mode 100644 index 00000000000..955fbf8be70 --- /dev/null +++ b/integrationExamples/gpt/captifyRtdProvider_example.html @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + +
+

+ Module will add key/value pairs in ad calls. + Check out for captify_segments key in the payload sent to Xandr to endpoint https://ib.adnxs.com/ut/v3/prebid : keywords.key[captify_segments] should have an array of string as value. + This array will have Xandr RTSS segment ids. +

+
+

Basic Prebid.js Example with CaptifyRTD

+
Div-1
+
+ +
+ + + diff --git a/integrationExamples/gpt/gpp_us_hello_world.html b/integrationExamples/gpt/gpp_us_hello_world.html new file mode 100644 index 00000000000..28be86127fc --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world.html @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+ +
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/gpp_us_hello_world_iframe.html b/integrationExamples/gpt/gpp_us_hello_world_iframe.html new file mode 100644 index 00000000000..c0a62f9d72e --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world_iframe.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html b/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html new file mode 100644 index 00000000000..8c2096d614d --- /dev/null +++ b/integrationExamples/gpt/gpp_us_hello_world_iframe_subpage.html @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+ +
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/growthcode.html b/integrationExamples/gpt/growthcode.html new file mode 100644 index 00000000000..ede51d2d869 --- /dev/null +++ b/integrationExamples/gpt/growthcode.html @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html old mode 100755 new mode 100644 diff --git a/integrationExamples/gpt/mgidRtdProvider_example.html b/integrationExamples/gpt/mgidRtdProvider_example.html new file mode 100644 index 00000000000..e3e4f720586 --- /dev/null +++ b/integrationExamples/gpt/mgidRtdProvider_example.html @@ -0,0 +1,143 @@ + + + + + + + + + + +JS Bin + + + +

Basic Prebid.js Example

+
Div-1
+ +
+ +
+ + + diff --git a/integrationExamples/gpt/neuwoRtdProvider_example.html b/integrationExamples/gpt/neuwoRtdProvider_example.html new file mode 100644 index 00000000000..f33b9f94c67 --- /dev/null +++ b/integrationExamples/gpt/neuwoRtdProvider_example.html @@ -0,0 +1,190 @@ + + + + + + + + + + +

Basic Prebid.js Example using neuwoRtdProvider

+
+ Looks like you're not following the testing environment setup, try accessing http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html + after running commands in the prebid.js source folder that includes libraries/modules/neuwoRtdProvider.js + + npm ci + npm i -g gulp-cli + gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter + +
+
+

Add token and url to use for Neuwo extension configuration

+ + + +
+ +
Div-1
+
+ Ad spot div-1: This content will be replaced by prebid.js and/or related components once you click "Update" +
+ +
+ +
Div-2
+
+ Ad spot div-2: Replaces this text as well, if everything goes to plan + + +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index 118cc678726..554f2081c6d 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -15,7 +15,8 @@ _papns: ['appnexus1', 'appnexus2'], _psegs: ['1234', '1000001', '1000002'], _ppam: ['ppam1', 'ppam2'], - _pcrprs: ['pcrprs1', 'pcrprs2'] + _pcrprs: ['pcrprs1', 'pcrprs2'], + _pssps: { ssps: ['appnexus', 'some other'], cohorts: ['abcd', 'efgh', 'ijkl'] }, } for (let key in data) { diff --git a/integrationExamples/gpt/topics_frame.html b/integrationExamples/gpt/topics_frame.html new file mode 100644 index 00000000000..7a12b030a6a --- /dev/null +++ b/integrationExamples/gpt/topics_frame.html @@ -0,0 +1,43 @@ + + + + Topics demo + + + + + + + + + \ No newline at end of file diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index 5a6c80c4adf..4f94c73c281 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -249,7 +249,17 @@ } }, { - "name": "uid2" + "name": "uid2", + "params": { + "uid2Token": { + "advertising_token": "example token", + "refresh_token": "aslkdjaslkjdaslkhj", + "identity_expires": Date.now() + 60*1000, + "refresh_from": Date.now() - 10*1000, + "refresh_expires": Date.now() + 12*60*60*1000, + "refresh_response_key": null + } + } } , { diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html index 73843c49914..7e75721103f 100644 --- a/integrationExamples/gpt/weboramaRtdProvider_example.html +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -1,12 +1,9 @@ - - - - - - weborama rtd submodule example - - + + + + + weborama rtd submodule example + @@ -33,16 +30,19 @@ weboCtxConf: { token: "to-be-defined", // mandatory targetURL: "https://prebid.org", // default is document.URL + assetID: "token:identifier", // new parameter, overrides url setPrebidTargeting: true, // override param.setPrebidTargeting or default true sendToBidders: true, // override param.sendToBidders or default true defaultProfile: { // optional webo_ctx: ["Rugby_Renault_c11495", "Sport_c11893"], webo_ds: ['bar'] }, + baseURLProfileAPI: 'ctx-preprod.weborama.com', // enabled: false, //, onData: function (data,...) { ...} }, weboUserDataConf: { + enabled: false, accountId: 12345, // recommended setPrebidTargeting: true, // override param.setPrebidTargeting or default true sendToBidders: ['smartadserver'], // specify the bidder to share data @@ -55,7 +55,7 @@ //, onData: function (data,...) { ...} }, sfbxLiteDataConf: { - enabled: true, + enabled: false, defaultProfile: { // optional lite_occupation: ['gérant', 'bénévole'], lite_hobbies: ['sport', 'cinéma'], @@ -118,46 +118,6 @@ networkId: 456456, }, }] - }, - { - code: '/1056029/webo-wam-prebid', - mediaTypes: { - banner: { - sizes: div_2_sizes - } - }, - bids: [{ - bidder: 'smartadserver', - params: { - siteId: 1234, - pageId: 1234, - formatId: 1234, - } - }, { - bidder: 'pubmatic', - params: { - publisherId: '32572', - } - }, { - bidder: 'appnexus', - params: { - placementId: 234234, - } - }, { - bidder: 'rubicon', - params: { - accountId: '14062', - siteId: '70608', - zoneId: '335918', - userId: '12346', - } - }, { - bidder: 'criteo', - params: { - zoneId: 234234, - networkId: 456456, - }, - }] } ]; @@ -197,7 +157,6 @@ googletag.cmd.push(function () { googletag.defineSlot('/1056029/webo-ctx-prebid', div_1_sizes, 'div-gpt-ad-1620653642627-0').addService(googletag.pubads()); - googletag.defineSlot('/1056029/webo-wam-prebid', div_2_sizes, 'div-gpt-ad-1645023761875-0').addService(googletag.pubads()); googletag.pubads().disableInitialLoad(); googletag.enableServices(); }); diff --git a/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html new file mode 100644 index 00000000000..044b4295e67 --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html @@ -0,0 +1,96 @@ + + + + + + + JW Player with Bid Marked As Used + + + + + + + +
+ + + diff --git a/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html new file mode 100644 index 00000000000..620f891fa50 --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html @@ -0,0 +1,89 @@ + + + + + + + JW Player with Bid Request Scheduling + + + + + + + +

JW Player with Bid Request Scheduling

+
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/jwplayer/eventListeners.html b/integrationExamples/videoModule/jwplayer/eventListeners.html new file mode 100644 index 00000000000..0b43ff44d6c --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/eventListeners.html @@ -0,0 +1,249 @@ +- + + + + + + + + + + + +
+ + + diff --git a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html new file mode 100644 index 00000000000..296a3265bd1 --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html @@ -0,0 +1,120 @@ + + + + + + + JW Player with GAM Ad Server Mediation + + + + + + + +

JW Player with GAM Ad Server Mediation

+
Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/jwplayer/mediaMetadata.html b/integrationExamples/videoModule/jwplayer/mediaMetadata.html new file mode 100644 index 00000000000..815f5c4d6d7 --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/mediaMetadata.html @@ -0,0 +1,81 @@ + + + + + + + JW Player with Media Metadata + + + + + + + + +

JW Player with Media Metadata

+ +
+ + + diff --git a/integrationExamples/videoModule/jwplayer/playlist.html b/integrationExamples/videoModule/jwplayer/playlist.html new file mode 100644 index 00000000000..b77a1ec05fc --- /dev/null +++ b/integrationExamples/videoModule/jwplayer/playlist.html @@ -0,0 +1,123 @@ + + + + + + + JW Player with Playlist + + + + + + + + +

JW Player with Playlist

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html b/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html new file mode 100644 index 00000000000..b645a58a4fc --- /dev/null +++ b/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + VideoJS with Bid Marked As Used + + + + + + + +

VideoJS with Bid Marked As Used

+
Div-1: Player placeholder div
+ + + + diff --git a/integrationExamples/videoModule/videojs/bidRequestScheduling.html b/integrationExamples/videoModule/videojs/bidRequestScheduling.html new file mode 100644 index 00000000000..48f8b8685e1 --- /dev/null +++ b/integrationExamples/videoModule/videojs/bidRequestScheduling.html @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + VideoJS with Bid Request Scheduling + + + + + + + +

VideoJS with Bid Request Scheduling

+
Div-1: Player placeholder div
+ + + + diff --git a/integrationExamples/videoModule/videojs/eventListeners.html b/integrationExamples/videoModule/videojs/eventListeners.html new file mode 100644 index 00000000000..16edaf4da41 --- /dev/null +++ b/integrationExamples/videoModule/videojs/eventListeners.html @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + VideoJS Event Listeners + + + + + + + + +

VideoJS Event Listeners

+ +
Div-1: Player placeholder div
+ + + + + diff --git a/integrationExamples/videoModule/videojs/gamAdServerMediation.html b/integrationExamples/videoModule/videojs/gamAdServerMediation.html new file mode 100644 index 00000000000..9efd77e9681 --- /dev/null +++ b/integrationExamples/videoModule/videojs/gamAdServerMediation.html @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + VideoJS with GAM Ad Server Mediation + + + + + + + +

VideoJS with GAM Ad Server Mediation

+
Div-1: Player placeholder div
+ + + + diff --git a/integrationExamples/videoModule/videojs/mediaMetadata.html b/integrationExamples/videoModule/videojs/mediaMetadata.html new file mode 100644 index 00000000000..eef12b0e19d --- /dev/null +++ b/integrationExamples/videoModule/videojs/mediaMetadata.html @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + VideoJS with Media Metadata + + + + + + + + +

VideoJS with Media Metadata

+ +
Div-1: Player placeholder div
+ + + + diff --git a/integrationExamples/videoModule/videojs/playlist.html b/integrationExamples/videoModule/videojs/playlist.html new file mode 100644 index 00000000000..a9e3d1bc99b --- /dev/null +++ b/integrationExamples/videoModule/videojs/playlist.html @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + VideoJS with Playlist + + + + + + + + +

VideoJS with Playlist

+ +
Div-1: Player placeholder div
+ + + + + diff --git a/karma.conf.maker.js b/karma.conf.maker.js index b20f73d74bc..e05d5b08afd 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -110,7 +110,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe var webpackConfig = newWebpackConfig(codeCoverage, disableFeatures); var plugins = newPluginsArray(browserstack); - var files = file ? ['test/test_deps.js', file] : ['test/test_index.js']; + var files = file ? ['test/test_deps.js', file, 'test/helpers/hookSetup.js'].flatMap(f => f) : ['test/test_index.js']; // This file opens the /debug.html tab automatically. // It has no real value unless you're running --watch, and intend to do some debugging in the browser. if (watchMode) { diff --git a/libraries/analyticsAdapter/AnalyticsAdapter.js b/libraries/analyticsAdapter/AnalyticsAdapter.js index 277a1455a75..b6e270b3c3c 100644 --- a/libraries/analyticsAdapter/AnalyticsAdapter.js +++ b/libraries/analyticsAdapter/AnalyticsAdapter.js @@ -1,47 +1,68 @@ import CONSTANTS from '../../src/constants.json'; -import { ajax } from '../../src/ajax.js'; -import { logMessage, _each } from '../../src/utils.js'; -import * as events from '../../src/events.js' +import {ajax} from '../../src/ajax.js'; +import {logError, logMessage} from '../../src/utils.js'; +import * as events from '../../src/events.js'; export const _internal = { ajax }; - -const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - REQUEST_BIDS, - BID_REQUESTED, - BID_TIMEOUT, - BID_RESPONSE, - NO_BID, - BID_WON, - BID_ADJUSTMENT, - BIDDER_DONE, - SET_TARGETING, - AD_RENDER_FAILED, - AD_RENDER_SUCCEEDED, - AUCTION_DEBUG, - ADD_AD_UNITS, - BILLABLE_EVENT - } -} = CONSTANTS; - const ENDPOINT = 'endpoint'; const BUNDLE = 'bundle'; +export const DEFAULT_INCLUDE_EVENTS = Object.values(CONSTANTS.EVENTS) + .filter(ev => ev !== CONSTANTS.EVENTS.AUCTION_DEBUG); + +let debounceDelay = 100; + +export function setDebounceDelay(delay) { + debounceDelay = delay; +} + export default function AnalyticsAdapter({ url, analyticsType, global, handler }) { - const _queue = []; - let _eventCount = 0; - let _enableCheck = true; - let _handlers; - let _enabled = false; - let _sampled = true; - - if (analyticsType === ENDPOINT || BUNDLE) { - _emptyQueue(); - } + const queue = []; + let handlers; + let enabled = false; + let sampled = true; + let provider; + + const emptyQueue = (() => { + let running = false; + let timer; + const clearQueue = () => { + if (!running) { + running = true; // needed to avoid recursive re-processing when analytics event handlers trigger other events + try { + let i = 0; + let notDecreasing = 0; + while (queue.length > 0) { + i++; + const len = queue.length; + queue.shift()(); + if (queue.length >= len) { + notDecreasing++; + } else { + notDecreasing = 0 + } + if (notDecreasing >= 10) { + logError('Detected probable infinite loop, discarding events', queue) + queue.length = 0; + return; + } + } + logMessage(`${provider} analytics: processed ${i} events`); + } finally { + running = false; + } + } + }; + return function () { + if (timer != null) { + clearTimeout(timer); + timer = null; + } + debounceDelay === 0 ? clearQueue() : timer = setTimeout(clearQueue, debounceDelay); + } + })(); return Object.defineProperties({ track: _track, @@ -54,7 +75,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } getUrl: () => url }, { enabled: { - get: () => _enabled + get: () => enabled } }); @@ -72,69 +93,58 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } _internal.ajax(url, callback, JSON.stringify({ eventType, args })); } - function _enqueue({ eventType, args }) { - const _this = this; - - if (global && window[global] && eventType && args) { - this.track({ eventType, args }); - } else { - _queue.push(function () { - _eventCount++; - _this.track({ eventType, args }); - }); - } + function _enqueue({eventType, args}) { + queue.push(() => { + this.track({eventType, args}); + }); + emptyQueue(); } function _enable(config) { + provider = config?.provider; var _this = this; if (typeof config === 'object' && typeof config.options === 'object') { - _sampled = typeof config.options.sampling === 'undefined' || Math.random() < parseFloat(config.options.sampling); + sampled = typeof config.options.sampling === 'undefined' || Math.random() < parseFloat(config.options.sampling); } else { - _sampled = true; + sampled = true; } - if (_sampled) { + if (sampled) { + const trackedEvents = (() => { + const {includeEvents = DEFAULT_INCLUDE_EVENTS, excludeEvents = []} = (config || {}); + return new Set( + Object.values(CONSTANTS.EVENTS) + .filter(ev => includeEvents.includes(ev)) + .filter(ev => !excludeEvents.includes(ev)) + ); + })(); + // first send all events fired before enableAnalytics called events.getEvents().forEach(event => { - if (!event) { + if (!event || !trackedEvents.has(event.eventType)) { return; } const { eventType, args } = event; - - if (eventType !== BID_TIMEOUT) { - _enqueue.call(_this, { eventType, args }); - } + _enqueue.call(_this, { eventType, args }); }); // Next register event listeners to send data immediately - - _handlers = { - [REQUEST_BIDS]: args => this.enqueue({ eventType: REQUEST_BIDS, args }), - [BID_REQUESTED]: args => this.enqueue({ eventType: BID_REQUESTED, args }), - [BID_RESPONSE]: args => this.enqueue({ eventType: BID_RESPONSE, args }), - [NO_BID]: args => this.enqueue({ eventType: NO_BID, args }), - [BID_TIMEOUT]: args => this.enqueue({ eventType: BID_TIMEOUT, args }), - [BID_WON]: args => this.enqueue({ eventType: BID_WON, args }), - [BID_ADJUSTMENT]: args => this.enqueue({ eventType: BID_ADJUSTMENT, args }), - [BIDDER_DONE]: args => this.enqueue({ eventType: BIDDER_DONE, args }), - [SET_TARGETING]: args => this.enqueue({ eventType: SET_TARGETING, args }), - [AUCTION_END]: args => this.enqueue({ eventType: AUCTION_END, args }), - [AD_RENDER_FAILED]: args => this.enqueue({ eventType: AD_RENDER_FAILED, args }), - [AD_RENDER_SUCCEEDED]: args => this.enqueue({ eventType: AD_RENDER_SUCCEEDED, args }), - [AUCTION_DEBUG]: args => this.enqueue({ eventType: AUCTION_DEBUG, args }), - [ADD_AD_UNITS]: args => this.enqueue({ eventType: ADD_AD_UNITS, args }), - [BILLABLE_EVENT]: args => this.enqueue({ eventType: BILLABLE_EVENT, args }), - [AUCTION_INIT]: args => { - args.config = typeof config === 'object' ? config.options || {} : {}; // enableAnaltyics configuration object - this.enqueue({ eventType: AUCTION_INIT, args }); - } - }; - - _each(_handlers, (handler, event) => { - events.on(event, handler); - }); + handlers = Object.fromEntries( + Array.from(trackedEvents) + .map((ev) => { + const handler = ev === CONSTANTS.EVENTS.AUCTION_INIT + ? (args) => { + // TODO: remove this special case in v8 + args.config = typeof config === 'object' ? config.options || {} : {}; + this.enqueue({eventType: ev, args}); + } + : (args) => this.enqueue({eventType: ev, args}); + events.on(ev, handler); + return [ev, handler]; + }) + ) } else { logMessage(`Analytics adapter for "${global}" disabled by sampling`); } @@ -144,31 +154,14 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } this.enableAnalytics = function _enable() { return logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); }; - _enabled = true; + enabled = true; } function _disable() { - _each(_handlers, (handler, event) => { + Object.entries(handlers || {}).forEach(([event, handler]) => { events.off(event, handler); - }); + }) this.enableAnalytics = this._oldEnable ? this._oldEnable : _enable; - _enabled = false; - } - - function _emptyQueue() { - if (_enableCheck) { - for (var i = 0; i < _queue.length; i++) { - _queue[i](); - } - - // override push to execute the command immediately from now on - _queue.push = function (fn) { - fn(); - }; - - _enableCheck = false; - } - - logMessage(`event count sent to ${global}: ${_eventCount}`); + enabled = false; } } diff --git a/libraries/ortb2.5StrictTranslator/dsl.js b/libraries/ortb2.5StrictTranslator/dsl.js new file mode 100644 index 00000000000..8f9fc79feeb --- /dev/null +++ b/libraries/ortb2.5StrictTranslator/dsl.js @@ -0,0 +1,54 @@ +export const ERR_TYPE = 0; // field has wrong type (only objects, enums, and arrays of objects or enums are checked) +export const ERR_UNKNOWN_FIELD = 1; // field is not defined in ORTB 2.5 spec +export const ERR_ENUM = 2; // field is an enum and its value is not one of those listed in the ORTB 2.5 spec + +// eslint-disable-next-line symbol-description +export const extend = Symbol(); + +export function Obj(primitiveFields, spec = {}) { + const scan = (path, parent, field, value, onError) => { + if (value == null || typeof value !== 'object') { + onError(ERR_TYPE, path, parent, field, value); + return; + } + Object.entries(value).forEach(([k, v]) => { + if (v == null) return; + const kpath = path == null ? k : `${path}.${k}`; + if (spec.hasOwnProperty(k)) { + spec[k](kpath, value, k, v, onError); + return; + } + if (k !== 'ext' && !primitiveFields.includes(k)) { + onError(ERR_UNKNOWN_FIELD, kpath, value, k, v); + } + }); + }; + scan[extend] = (extraPrimitives, specOverride = {}) => + Obj(primitiveFields.concat(extraPrimitives), Object.assign({}, spec, specOverride)); + return scan; +} + +export const ID = Obj(['id']); +export const Named = ID[extend](['name']); + +export function Arr(def) { + return (path, parent, field, value, onError) => { + if (!Array.isArray(value)) { + onError(ERR_TYPE, path, parent, field, value); + } else { + value.forEach((item, i) => def(`${path}.${i}`, value, i, item, onError)); + } + }; +} + +export function IntEnum(min, max) { + return (path, parent, field, value, onError) => { + const errno = (() => { + if (typeof value !== 'number') return ERR_TYPE; + if (isNaN(value) || value > max || value < min) return ERR_ENUM; + })(); + if (errno != null) { + onError(errno, path, parent, field, value); + } + }; +} diff --git a/libraries/ortb2.5StrictTranslator/spec.js b/libraries/ortb2.5StrictTranslator/spec.js new file mode 100644 index 00000000000..0ffb17a2e72 --- /dev/null +++ b/libraries/ortb2.5StrictTranslator/spec.js @@ -0,0 +1,81 @@ +import {Arr, extend, ID, IntEnum, Named, Obj} from './dsl.js'; + +const CatDomain = Named[extend](['cat', 'domain']); +const Segment = Named[extend](['value']); +const Data = Named[extend]([], { + segment: Arr(Segment) +}); +const Content = ID[extend](['episode', 'title', 'series', 'season', 'artist', 'genre', 'album', 'isrc', 'url', 'cat', 'contentrating', 'userrating', 'keywords', 'livestream', 'sourcerelationship', 'len', 'language', 'embeddable'], { + producer: CatDomain, + data: Arr(Data), + prodq: IntEnum(0, 3), + videoquality: IntEnum(0, 3), + context: IntEnum(1, 7), + qagmediarating: IntEnum(1, 3), +}); + +const Client = CatDomain[extend](['sectioncat', 'pagecat', 'privacypolicy', 'keywords'], { + publisher: CatDomain, content: Content, +}); +const Site = Client[extend](['page', 'ref', 'search', 'mobile']); +const App = Client[extend](['bundle', 'storeurl', 'ver', 'paid']); + +const Geo = Obj(['lat', 'lon', 'accuracy', 'lastfix', 'country', 'region', 'regionfips104', 'metro', 'city', 'zip', 'utcoffset'], { + type: IntEnum(1, 3), + ipservice: IntEnum(1, 4) +}); +const Device = Obj(['ua', 'dnt', 'lmt', 'ip', 'ipv6', 'make', 'model', 'os', 'osv', 'hwv', 'h', 'w', 'ppi', 'pxratio', 'js', 'geofetch', 'flashver', 'language', 'carrier', 'mccmnc', 'ifa', 'didsha1', 'didmd5', 'dpidsha1', 'dpidmd5', 'macsha1', 'macmd5'], { + geo: Geo, devicetype: IntEnum(1, 7), connectiontype: IntEnum(0, 6) +}); +const User = ID[extend](['buyeruid', 'yob', 'gender', 'keywords', 'customdata'], { + geo: Geo, data: Arr(Data), +}); + +const Floorable = ID[extend](['bidfloor', 'bidfloorcur']); +const Deal = Floorable[extend](['at', 'wseat', 'wadomain']); +const Pmp = Obj(['private_auction'], { + deals: Arr(Deal), +}); +const Format = Obj(['w', 'h', 'wratio', 'hratio', 'wmin']); +const MediaType = Obj(['mimes'], { + api: Arr(IntEnum(1, 6)), battr: Arr(IntEnum(1, 17)) +}); +const Banner = MediaType[extend](['id', 'w', 'h', 'wmax', 'hmax', 'hmin', 'wmin', 'topframe', 'vcm'], { + format: Arr(Format), btype: Arr(IntEnum(1, 4)), pos: IntEnum(0, 7), expdir: Arr(IntEnum(1, 5)) +}); +const Native = MediaType[extend](['request', 'ver']); +const RichMediaType = MediaType[extend](['minduration', 'maxduration', 'startdelay', 'sequence', 'maxextended', 'minbitrate', 'maxbitrate'], { + protocols: Arr(IntEnum(1, 10)), + delivery: Arr(IntEnum(1, 3)), + companionad: Arr(Banner), + companiontype: Arr(IntEnum(1, 3)), +}); +/* +const Audio = RichMediaType[extend](['maxseq', 'stitched'], { + feed: IntEnum(1, 3), nvol: IntEnum(0, 4), +}); + */ +const Video = RichMediaType[extend](['w', 'h', 'skip', 'skipmin', 'skipafter', 'boxingallowed'], { + pos: IntEnum(0, 7), + protocol: IntEnum(1, 10), + placement: IntEnum(1, 5), + linearity: IntEnum(1, 2), + playbackmethod: Arr(IntEnum(1, 6)), + playbackend: IntEnum(1, 3), +}); +const Metric = Obj(['type', 'value', 'vendor']); +const Imp = (() => { + const spec = { + metric: Arr(Metric), banner: Banner, video: Video, pmp: Pmp, + }; + if (FEATURES.NATIVE) { + spec.native = Native; + } + return Floorable[extend](['displaymanager', 'displaymanagerver', 'instl', 'tagid', 'clickbrowser', 'secure', 'iframebuster', 'exp'], spec); +})(); + +const Regs = Obj(['coppa']); +const Source = Obj(['fd', 'tid', 'pchain']); +export const BidRequest = ID[extend](['test', 'at', 'tmax', 'wseat', 'bseat', 'allimps', 'cur', 'wlang', 'bcat', 'badv', 'bapp'], { + imp: Arr(Imp), site: Site, app: App, device: Device, user: User, source: Source, regs: Regs +}); diff --git a/libraries/ortb2.5StrictTranslator/translator.js b/libraries/ortb2.5StrictTranslator/translator.js new file mode 100644 index 00000000000..c6f651e2476 --- /dev/null +++ b/libraries/ortb2.5StrictTranslator/translator.js @@ -0,0 +1,37 @@ +import {BidRequest} from './spec.js'; +import {logWarn} from '../../src/utils.js'; +import {toOrtb25} from '../ortb2.5Translator/translator.js'; + +function deleteField(errno, path, obj, field, value) { + logWarn(`${path} is not valid ORTB 2.5, field will be removed from request:`, value); + Array.isArray(obj) ? obj.splice(field, 1) : delete obj[field]; +} + +/** + * Translates an ortb request to 2.5, and removes from the result any field that is: + * - not defined in the 2.5 spec, or + * - defined as an enum, but has a value that is not listed in the 2.5 spec. + * + * `ortb2` is modified in place and returned. + * + * Note that using this utility will cause your adapter to pull in an additional ~3KB after minification. + * If possible, consider making your endpoint tolerant to unrecognized or invalid fields instead. + * + * + * @param ortb2 ORTB request + * @param translator translation function. The default moves 2.x fields that have a known standard location in 2.5. + * See the `ortb2.5Translator` library. + * @param onError a function invoked once for each field that is not valid according to the 2.5 spec; it takes + * (errno, path, obj, field, value), where: + * - errno is an error code (defined in dsl.js) + * - path is the JSON path of the offending field, for example `regs.gdpr` + * - obj is the object containing the offending field, for example `ortb2.regs` + * - field is the field name, for example `'gdpr'` + * - value is `obj[field]`. + * The default logs a warning and deletes the offending field. + */ +export function toOrtb25Strict(ortb2, translator = toOrtb25, onError = deleteField) { + ortb2 = translator(ortb2); + BidRequest(null, null, null, ortb2, onError); + return ortb2; +} diff --git a/libraries/ortb2.5Translator/translator.js b/libraries/ortb2.5Translator/translator.js new file mode 100644 index 00000000000..1afad516ef0 --- /dev/null +++ b/libraries/ortb2.5Translator/translator.js @@ -0,0 +1,82 @@ +import {deepAccess, deepSetValue, logError} from '../../src/utils.js'; + +export const EXT_PROMOTIONS = [ + 'source.schain', + 'regs.gdpr', + 'regs.us_privacy', + 'regs.gpp', + 'user.consent', + 'user.eids' +]; + +export function splitPath(path) { + const parts = path.split('.'); + const prefix = parts.slice(0, parts.length - 1).join('.'); + const field = parts[parts.length - 1]; + return [prefix, field]; +} + +/** + * @param sourcePath a JSON path such as `regs.us_privacy` + * @param dest {function(String, String): String} a function taking (prefix, field) and returning a destination path; + * for example, ('regs', 'us_privacy') => 'regs.ext.us_privacy' + * @return {(function({}): (function(): void|undefined))|*} a function that takes an object and, if it contains + * sourcePath, copies its contents to destinationPath, returning a function that deletes the original sourcePath. + */ +export function moveRule(sourcePath, dest = (prefix, field) => `${prefix}.ext.${field}`) { + const [prefix, field] = splitPath(sourcePath); + dest = dest(prefix, field); + return (ortb2) => { + const obj = deepAccess(ortb2, prefix); + if (obj?.[field] != null) { + deepSetValue(ortb2, dest, obj[field]); + return () => delete obj[field]; + } + }; +} + +function kwarrayRule(section) { + // move 2.6 `kwarray` into 2.5 comma-separated `keywords`. + return (ortb2) => { + const kwarray = ortb2[section]?.kwarray; + if (kwarray != null) { + let kw = (ortb2[section].keywords || '').split(','); + if (Array.isArray(kwarray)) kw.push(...kwarray); + ortb2[section].keywords = kw.join(','); + return () => delete ortb2[section].kwarray; + } + }; +} + +export const DEFAULT_RULES = Object.freeze([ + ...EXT_PROMOTIONS.map((f) => moveRule(f)), + ...['app', 'content', 'site', 'user'].map(kwarrayRule) +]); + +/** + * Factory for ORTB 2.5 translation functions. + * + * @param deleteFields if true, the translation function will remove fields that have been translated (transferred somewhere else within the request) + * @param rules translation rules; an array of functions of the type returned by `moveRule` + * @return {function({}): {}} a translation function that takes an ORTB object, modifies it in place, and returns it. + */ +export function ortb25Translator(deleteFields = true, rules = DEFAULT_RULES) { + return function (ortb2) { + rules.forEach(f => { + try { + const deleter = f(ortb2); + if (typeof deleter === 'function' && deleteFields) deleter(); + } catch (e) { + logError('Error translating request to ORTB 2.5', e); + } + }) + return ortb2; + } +} + +/** + * Translate an ortb request to version 2.5 by moving 2.6 (and later) fields that have a standardized 2.5 extension. + * + * The request is modified in place and returned. + */ +export const toOrtb25 = ortb25Translator(); diff --git a/libraries/ortbConverter/README.md b/libraries/ortbConverter/README.md new file mode 100644 index 00000000000..31f56b4c754 --- /dev/null +++ b/libraries/ortbConverter/README.md @@ -0,0 +1,378 @@ +# Prebid.js - ORTB conversion library + +This library provides methods to convert Prebid.js bid request objects to ORTB requests, +and ORTB responses to Prebid.js bid response objects. + +## Usage + +The simplest way to use this from an adapter is: + +```javascript +import {ortbConverter} from '../../libraries/ortbConverter/converter.js' + +const converter = ortbConverter({ + context: { + // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 30 // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + } +}); + +registerBidder({ + // ... rest of your spec goes here ... + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}) + // you may need to adjust `data` to suit your needs - see "customization" below + return [{ + method: METHOD, + url: ENDPOINT_URL, + data + }] + }, + interpretResponse(response, request) { + const bids = converter.fromORTB({response: response.body, request: request.data}).bids; + // likewise, you may need to adjust the bid response objects + return bids; + }, +}) +``` + +Without any customization, the library will generate complete ORTB requests, but ignores your [bid params](#params). +If your endpoint sets `response.seatbid[].bid[].mtype` (part of the ORTB 2.6 spec), it will also parse the response into complete bidResponse objects. See [setting response mediaTypes](#response-mediaTypes) if that is not the case. + +### Module-specific conversions + +Prebid.js features that require a module also require it for their corresponding ORTB conversion logic. For example, `imp.bidfloor` is only populated if the `priceFloors` module is active; `request.cur` needs the `currency` module, and so on. Notably, this means that to get those fields populated from your unit tests, you must import those modules first; see [this suite](https://github.com/prebid/Prebid.js/blob/master/test/spec/modules/openxOrtbBidAdapter_spec.js) for an example. + +## Customization + +### Modifying return values directly + +You are free to modify the objects returned by both `toORTB` and `fromORTB`: + +```javascript +const data = converter.toORTB({bidRequests, bidderRequest}); +deepSetValue(data.imp[0], 'ext.myCustomParam', bidRequests[0].params.myCustomParam); +``` + +However, there are two restrictions (to avoid them, use the [other customization options](#fine-customization)): + + - you may not change the `imp[].id` returned by `toORTB`; they ared used internally to match responses to their requests. + ```javascript + const data = converter.toORTB({bidRequests, bidderRequest}); + data.imp[0].id = 'custom-imp-id' // do not do this - it will cause an error later in `fromORTB` + ``` + See also [overriding `imp.id`](#imp-id). + - the `request` argument passed to `fromORTB` must be the same object returned by `toORTB`. + ```javascript + let data = converter.toORTB({bidRequests, bidderRequest}); + + data = mergeDeep( // the original object is lost + {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, // `fromORTB` will later throw an error + data + ); + + // do this instead: + mergeDeep( + data, + {ext: {myCustomParam: bidRequests[0].params.myCustomParam}}, + data + ) + ``` + + +### Fine grained customization - imp, request, bidResponse, response + +When invoked, `toORTB({bidRequests, bidderRequest})` first loops through each request in `bidRequests`, converting them into ORTB `imp` objects. +It then packages them into a single ORTB request, adding other parameters that are not imp-specific (such as for example `request.tmax`). + +Likewise, `fromORTB({request, response})` first loops through each `response.seatbid[].bid[]`, converting them into Prebid bidResponses; it then packages them into +a single return value. + +You can customize each of these steps using the `ortbConverter` arguments `imp`, `request`, `bidResponse` and `response`: + +### Customizing imps: `imp(buildImp, bidRequest, context)` + +Invoked once for each input `bidRequest`; should return the ORTB `imp` object to include in the request. +The arguments are: + +- `buildImp`: a function taking `(bidRequest, context)` and returning an ORTB `imp` object; +- `bidRequest`: the bid request object to convert; +- `context`: a [context object](#context) that contains at least: + - `bidderRequest`: the `bidderRequest` argument passed to `toORTB`. + +#### Example: attaching custom bid params + +```javascript +const converter = ortbConverter({ + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext.params', bidRequest.params); + return imp; + } +}) +``` + +#### Example: overriding imp.id + +```javascript +const converter = ortbConverter({ + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + imp.id = randomIdentifierStr(); + return imp; + } +}) +``` + +### Customizing the request: `request(buildRequest, imps, bidderRequest, context)` + +Invoked once after all bidRequests have been converted into `imp`s; should return the complete ORTB request. The return value +of this function is also the return value of `toORTB`. +The arguments are: + +- `buildRequest`: a function taking `(imps, bidderRequest, context)` and returning an ORTB request object; +- `imps` an array of ORTB `imp` objects that should be included in the request; +- `bidderRequest`: the `bidderRequest` argument passed to `toORTB`; +- `context`: a [context object](#context) that contains at least: + - `bidRequests`: the `bidRequests` argument passed to `toORTB`. + +#### Example: setting additional request properties + +```javascript +const converter = ortbConverter({ + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'ext.adapterVersion', '0.0.1'); + return request; + } +}) +``` + +### Customizing bid responses: `bidResponse(buildBidResponse, bid, context)` + +Invoked once for each `seatbid[].bid[]` in the response; should return the corresponding Prebid.js bid response object. +The arguments are: +- `buildBidResponse`: a function taking `(bid, context)` and returning a Prebid.js bid response object; +- `bid`: an ORTB `seatbid[].bid[]` object; +- `context`: a [context object](#context) that contains at least: + - `seatbid`: the ORTB `seatbid[]` object that encloses `bid`; + - `imp`: the ORTB request's `imp` object that matches `bid.impid`; + - `bidRequest`: the Prebid.js bid request object that was used to generate `context.imp`; + - `ortbRequest`: the `request` argument passed to `fromORTB`; + - `ortbResponse`: the `response` argument passed to `fromORTB`. + +#### Example: setting a custom outstream renderer + +```javascript +const converter = ortbConverter({ + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + const {bidRequest} = context; + if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = Renderer.install({ + url: RENDERER_URL, + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode + }); + } + return bidResponse; + } +}) +``` + +#### Example: setting response mediaType + +In ORTB 2.5, bid responses do not specify their mediatype, which is something Prebid.js requires. You can provide it as +`context.mediaType`: + +```javascript +const converter = ortbConverter({ + bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.mediaType'); + return buildBidResponse(bid, context) + } +}) +``` + +If you know that a particular ORTB request/response pair deals with exclusively one mediaType, you may also pass it directly in the [context parameter](#context). +Note that - compared to the above - this has additional effects, because `context.mediaType` is also considered during `imp` generation - see [special context properties](#special-context). + +```javascript +converter.toORTB({ + bidRequests: bidRequests.filter(isVideoBid), + bidderRequest, + context: {mediaType: 'video'} // make everything in this request/response deal with video only +}) +``` + +Note that this will _not_ work as intended: + +```javascript + +const converter = ortbConverter({ + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); // this throws; buildBidResponse needs to know the + // mediaType to properly populate bidResponse.ad, + // bidResponse.native etc + bidResponse.mediaType = deepAccess(bid, 'ext.mediaType'); // too late, use context.mediaType + return bidResponse; + } +}); + +``` + +### Customizing the response: `response(buildResponse, bidResponses, ortbResponse, context)` + +Invoked once, after all `seatbid[].bid[]` objects have been converted to corresponding bid responses. The value returned +by this function is also the value returned by `fromORTB`. +The arguments are: + +- `buildResponse`: a function that takes `(bidResponses, ortbResponse, context)` and returns `{bids: bidResponses}`. In the future, this may contain additional response data not necessarily tied to any bid (for example fledge auction configuration). +- `bidResponses`: array of Prebid.js bid response objects +- `ortbResponse`: the `response` argument passed to `fromORTB` +- `context`: a [context object](#context) that contains at least: + - `ortbRequest`: the `request` argument passed to `fromORTB`; + - `bidderRequest`: the `bidderRequest` argument passed to `toORTB`; + - `bidRequests`: the `bidRequests` argument passed to `toORTB`. + +#### Example: logging server-side errors + +```javascript +const converter = ortbConverter({ + response(buildResponse, bidResponses, ortbResponse, context) { + (deepAccess(ortbResponse, 'ext.errors') || []).forEach((e) => logWarn('Server error', e)); + return buildResponse(bidResponses, ortbResponse, context); + } +}) +``` + +### Even finer grained customization - processor overrides + +Each of the four conversion steps described above - imp, request, bidResponse and response - is further broken down into +smaller units of work (called _processors_). For example, when the currency module is included, it adds a _request processor_ +that sets `request.cur`; the priceFloors module adds an _imp processor_ that sets `imp.bidfloor` and `imp.bidfloorcur`, and so on. + +Each processor can be overridden or disabled through the `overrides` argument: + +#### Example: disabling currency +```javascript +const converter = ortbConverter({ + overrides: { + request: { + currency: false + } + } +}) +``` + +The above is similar in effect to: + +```javascript +const converter = ortbConverter({ + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + delete request.cur; + return request; + } +}) +``` + +With the main difference being that setting `currency: false` will disable currency logic entirely, while the `request` +version will still set `request.cur`, then delete it. If the currency processor is ever updated to deal with more than just `request.cur`, the `request` +function will also need to be updated accordingly. + +#### Example: taking video parameters from `bidRequest.params.video` + +Processors can also be overridden: + +```javascript +const converter = ortbConverter({ + overrides: { + imp: { + video(orig, imp, bidRequest, context) { + // `orig` is the video imp processor, which looks at bidRequest.mediaTypes[VIDEO] + // to populate imp.video + // alter its input `bidRequest` to also pick up parameters from `bidRequest.params` + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + } + } + } +}); +``` + +#### Processor override functions + +Processor overrides are similar to the override options described above, except that they take the object to process as argument: + +- `imp` processor overrides take `(orig, imp, bidRequest, context)`, where: + - `orig` is the processor function being overridden, which itself takes `(imp, bidRequest, context)`; + - `imp` is the (partial) imp object to modify; + - `bidRequest` and `context` are the same arguments passed to [imp](#imp). +- `request` processor overrides take `(orig, ortbRequest, bidderRequest, context)`, where: + - `orig` is the processor function being overridden, and takes `(ortbRequest, bidderRequest, context)`; + - `ortbRequest` is the partial request to modify; + - `bidderRequest` and `context` are the same arguments passed to [request](#reuqest). +- `bidResponse` processor overrides take `(orig, bidResponse, bid, context)`, where: + - `orig` is the processor function being overridden, and takes `(bidResponse, bid, context)`; + - `bidResponse` is the partial bid response to modify; + - `bid` and `context` are the same arguments passed to [bidResponse](#bidResponse) +- `response` processor overrides take `(orig, response, ortbResponse, context)`, where: + - `orig` is the processor function being overriden, and takes `(response, ortbResponse, context)`; + - `response` is the partial response to modify; + - `ortbRespones` and `context` are the same arguments passed to [response](#response). + +### The `context` argument + +All customization functions take a `context` argument. This is a plain JS object that is shared between `request` and its corresponding `response`; and between `imp` and its corresponding `bidResponse`: + +```javascript +const converter = ortbConverter({ + imp(buildImp, bidRequest, context) { + // `context` here will be later passed to `bidResponse` (if one matches the imp generated here) + context.someData = somethingInterestingAbout(bidRequest); + return buildImp(bidRequest, context); + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + doSomethingWith(context.someData); + return bidResponse; + } +}) +``` + +`ortbConverter` automatically populates `context` with some values of interest, such as `bidRequest`, `bidderRequest`, etc - as detailed above. In addition, you may pass additional context properties through: + +- the `context` argument of `ortbConverter`: e.g. `ortbConverter({context: {ttl: 30}})`. This will set `context.ttl = 30` globally for the converter. +- the `context` argument of `toORTB`: e.g. `converter.toORTB({bidRequests, bidderRequest, context: {ttl: 30}})`. This will set `context.ttl = 30` only for this request. + +### Special `context` properties + +For ease of use, the conversion logic gives special meaning to some context properties: + + - `currency`: a currency string (e.g. `'EUR'`). If specified, overrides the currency to use for computing price floors and `request.cur`. If omitted, both default to `getConfig('currency.adServerCurrency')`. + - `mediaType`: a bid mediaType (`'banner'`, `'video'`, or `'native'`). If specified: + - disables `imp` generation for other media types (i.e., if `context.mediaType === 'banner'`, only `imp.banner` will be populated; `imp.video` and `imp.native` will not, even if the bid request specifies them); + - is passed as the `mediaType` option to `bidRequest.getFloor` when computing price floors; + - sets `bidResponse.mediaType`. + - `nativeRequest`: a plain object that serves as the base value for `imp.native.request` (and is relevant only for native bid requests). + If not specified, the only property that is guaranteed to be populated is `assets`, since Prebid does not require anything else to define a native adUnit. You can use `context.nativeRequest` to provide other properties; for example, you may want to signal support for native impression trackers by setting it to `{eventtrackers: [{event: 1, methods: [1, 2]}]}` (see also the [ORTB Native spec](https://www.iab.com/wp-content/uploads/2018/03/OpenRTB-Native-Ads-Specification-Final-1.2.pdf)). + - `netRevenue`: the value to set as `bidResponse.netRevenue`. This is a required property of bid responses that does not have a clear ORTB counterpart. + - `ttl`: the default value to use for `bidResponse.ttl` (if the ORTB response does not provide one in `seatbid[].bid[].exp`). + +## Prebid Server extensions + +If your endpoint is a Prebid Server instance, you may take advantage of the `pbsExtension` companion library, which adds a number of processors that can populate and parse PBS-specific extensions (typically prefixed `ext.prebid`); these include bidder params (with `transformBidParams`), bidder aliases, targeting keys, and others. + +```javascript +import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js' + +const pbsConverter = ortbConverter({ + processors: pbsExtensions +}) +``` diff --git a/libraries/ortbConverter/converter.js b/libraries/ortbConverter/converter.js new file mode 100644 index 00000000000..2057af8b6d3 --- /dev/null +++ b/libraries/ortbConverter/converter.js @@ -0,0 +1,135 @@ +import {compose} from './lib/composer.js'; +import {logError, memoize} from '../../src/utils.js'; +import {DEFAULT_PROCESSORS} from './processors/default.js'; +import {BID_RESPONSE, DEFAULT, getProcessors, IMP, REQUEST, RESPONSE} from '../../src/pbjsORTB.js'; +import {mergeProcessors} from './lib/mergeProcessors.js'; + +export function ortbConverter({ + context: defaultContext = {}, + processors = defaultProcessors, + overrides = {}, + imp, + request, + bidResponse, + response, +} = {}) { + const REQ_CTX = new WeakMap(); + + function builder(slot, wrapperFn, builderFn, errorHandler) { + let build; + return function () { + if (build == null) { + build = (function () { + let delegate = builderFn.bind(this, compose(processors()[slot] || {}, overrides[slot] || {})); + if (wrapperFn) { + delegate = wrapperFn.bind(this, delegate); + } + return function () { + try { + return delegate.apply(this, arguments); + } catch (e) { + errorHandler.call(this, e, ...arguments); + } + } + })(); + } + return build.apply(this, arguments); + } + } + + const buildImp = builder(IMP, imp, + function (process, bidRequest, context) { + const imp = {}; + process(imp, bidRequest, context); + return imp; + }, + function (error, bidRequest, context) { + logError('Error while converting bidRequest to ORTB imp; request skipped.', {error, bidRequest, context}); + } + ); + + const buildRequest = builder(REQUEST, request, + function (process, imps, bidderRequest, context) { + const ortbRequest = {imp: imps}; + process(ortbRequest, bidderRequest, context); + return ortbRequest; + }, + function (error, imps, bidderRequest, context) { + logError('Error while converting to ORTB request', {error, imps, bidderRequest, context}); + throw error; + } + ); + + const buildBidResponse = builder(BID_RESPONSE, bidResponse, + function (process, bid, context) { + const bidResponse = {}; + process(bidResponse, bid, context); + return bidResponse; + }, + function (error, bid, context) { + logError('Error while converting ORTB seatbid.bid to bidResponse; bid skipped.', {error, bid, context}); + } + ); + + const buildResponse = builder(RESPONSE, response, + function (process, bidResponses, ortbResponse, context) { + const response = {bids: bidResponses}; + process(response, ortbResponse, context); + return response; + }, + function (error, bidResponses, ortbResponse, context) { + logError('Error while converting from ORTB response', {error, bidResponses, ortbResponse, context}); + throw error; + } + ); + + return { + toORTB({bidderRequest, bidRequests, context = {}}) { + bidRequests = bidRequests || bidderRequest.bids; + const ctx = { + req: Object.assign({bidRequests}, defaultContext, context), + imp: {} + } + const imps = bidRequests.map(bidRequest => { + const impContext = Object.assign({bidderRequest, reqContext: ctx.req}, defaultContext, context); + const result = buildImp(bidRequest, impContext); + if (result != null) { + if (result.hasOwnProperty('id')) { + impContext.bidRequest = bidRequest; + ctx.imp[result.id] = impContext; + return result; + } + logError('Converted ORTB imp does not specify an id, ignoring bid request', bidRequest, result); + } + }).filter(Boolean); + + const request = buildRequest(imps, bidderRequest, ctx.req); + ctx.req.bidderRequest = bidderRequest; + if (request != null) { + REQ_CTX.set(request, ctx); + } + return request; + }, + fromORTB({request, response}) { + const ctx = REQ_CTX.get(request); + if (ctx == null) { + throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`') + } + function augmentContext(ctx, extraParams = {}) { + return Object.assign({ortbRequest: request}, extraParams, ctx); + } + const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp])); + const bidResponses = (response.seatbid || []).flatMap(seatbid => + (seatbid.bid || []).map((bid) => { + if (impsById.hasOwnProperty(bid.impid) && ctx.imp.hasOwnProperty(bid.impid)) { + return buildBidResponse(bid, augmentContext(ctx.imp[bid.impid], {imp: impsById[bid.impid], seatbid, ortbResponse: response})); + } + logError('ORTB response seatbid[].bid[].impid does not match any imp in request; ignoring bid', bid); + }) + ).filter(Boolean); + return buildResponse(bidResponses, response, augmentContext(ctx.req)); + } + } +} + +export const defaultProcessors = memoize(() => mergeProcessors(DEFAULT_PROCESSORS, getProcessors(DEFAULT))); diff --git a/libraries/ortbConverter/lib/composer.js b/libraries/ortbConverter/lib/composer.js new file mode 100644 index 00000000000..0ceff7f9edb --- /dev/null +++ b/libraries/ortbConverter/lib/composer.js @@ -0,0 +1,43 @@ +const SORTED = new WeakMap(); + +/** + * @typedef {Object} Component + * A component function, that can be composed with other compatible functions into one. + * Compatible functions take the same arguments; return values are ignored. + * + * @property {function} fn the component function; + * @property {number} priority determines the order in which this function will run when composed with others. + */ + +/** + * + * @param {Object[string, Component]} components to compose + * @param {Object[string, function|boolean]} overrides a map from component name, to a function that should override that component. + * Override functions are replacements, except that they get the original function they are overriding as their first argument. If the override + * is `false`, the component is disabled. + * + * @return a function that will run all components in order of priority, with functions from `overrides` taking + * precedence over components that match names + */ +export function compose(components, overrides = {}) { + if (!SORTED.has(components)) { + const sorted = Object.entries(components); + sorted.sort((a, b) => { + a = a[1].priority || 0; + b = b[1].priority || 0; + return a === b ? 0 : a > b ? -1 : 1 + }); + SORTED.set(components, sorted.map(([name, cmp]) => [name, cmp.fn])) + } + const fns = SORTED.get(components) + .filter(([name]) => !overrides.hasOwnProperty(name) || overrides[name]) + .map(function ([name, fn]) { + return overrides.hasOwnProperty(name) ? overrides[name].bind(this, fn) : fn; + }); + return function () { + const args = Array.from(arguments); + fns.forEach(fn => { + fn.apply(this, args); + }) + } +} diff --git a/libraries/ortbConverter/lib/mergeProcessors.js b/libraries/ortbConverter/lib/mergeProcessors.js new file mode 100644 index 00000000000..357cecd45aa --- /dev/null +++ b/libraries/ortbConverter/lib/mergeProcessors.js @@ -0,0 +1,9 @@ +import {PROCESSOR_TYPES} from '../../../src/pbjsORTB.js'; + +export function mergeProcessors(...processors) { + const left = processors.shift(); + const right = processors.length > 1 ? mergeProcessors(...processors) : processors[0]; + return Object.fromEntries( + PROCESSOR_TYPES.map(type => [type, Object.assign({}, left[type], right[type])]) + ) +} diff --git a/libraries/ortbConverter/lib/sizes.js b/libraries/ortbConverter/lib/sizes.js new file mode 100644 index 00000000000..16b75048203 --- /dev/null +++ b/libraries/ortbConverter/lib/sizes.js @@ -0,0 +1,14 @@ +import {parseSizesInput} from '../../../src/utils.js'; + +export function sizesToFormat(sizes) { + sizes = parseSizesInput(sizes); + + // get sizes in form [{ w: , h: }, ...] + return sizes.map(size => { + const [width, height] = size.split('x'); + return { + w: parseInt(width, 10), + h: parseInt(height, 10) + }; + }); +} diff --git a/libraries/ortbConverter/processors/banner.js b/libraries/ortbConverter/processors/banner.js new file mode 100644 index 00000000000..51c93b652ef --- /dev/null +++ b/libraries/ortbConverter/processors/banner.js @@ -0,0 +1,40 @@ +import {createTrackPixelHtml, deepAccess, inIframe, mergeDeep} from '../../../src/utils.js'; +import {BANNER} from '../../../src/mediaTypes.js'; +import {sizesToFormat} from '../lib/sizes.js'; + +/** + * fill in a request `imp` with banner parameters from `bidRequest`. + */ +export function fillBannerImp(imp, bidRequest, context) { + if (context.mediaType && context.mediaType !== BANNER) return; + + const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); + if (bannerParams) { + const banner = { + topframe: inIframe() === true ? 0 : 1 + }; + if (bannerParams.sizes) { + banner.format = sizesToFormat(bannerParams.sizes); + } + if (bannerParams.hasOwnProperty('pos')) { + banner.pos = bannerParams.pos; + } + + imp.banner = mergeDeep(banner, imp.banner); + } +} + +export function bannerResponseProcessor({createPixel = (url) => createTrackPixelHtml(decodeURIComponent(url))} = {}) { + return function fillBannerResponse(bidResponse, bid) { + if (bidResponse.mediaType === BANNER) { + if (bid.adm && bid.nurl) { + bidResponse.ad = bid.adm; + bidResponse.ad += createPixel(bid.nurl); + } else if (bid.adm) { + bidResponse.ad = bid.adm; + } else if (bid.nurl) { + bidResponse.adUrl = bid.nurl; + } + } + }; +} diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js new file mode 100644 index 00000000000..1d6bfb8424e --- /dev/null +++ b/libraries/ortbConverter/processors/default.js @@ -0,0 +1,153 @@ +import {deepSetValue, logWarn, mergeDeep} from '../../../src/utils.js'; +import {bannerResponseProcessor, fillBannerImp} from './banner.js'; +import {fillVideoImp, fillVideoResponse} from './video.js'; +import {setResponseMediaType} from './mediaType.js'; +import {fillNativeImp, fillNativeResponse} from './native.js'; +import {BID_RESPONSE, IMP, REQUEST} from '../../../src/pbjsORTB.js'; +import {config} from '../../../src/config.js'; + +export const DEFAULT_PROCESSORS = { + [REQUEST]: { + fpd: { + // sets initial request to bidderRequest.ortb2 + priority: 99, + fn(ortbRequest, bidderRequest) { + mergeDeep(ortbRequest, bidderRequest.ortb2) + } + }, + // override FPD app, site, and device with getConfig('app'), etc if defined + // TODO: these should be deprecated for v8 + appFpd: fpdFromTopLevelConfig('app'), + siteFpd: fpdFromTopLevelConfig('site'), + deviceFpd: fpdFromTopLevelConfig('device'), + onlyOneClient: { + priority: -99, + fn: onlyOneClientSection + }, + props: { + // sets request properties id, tmax, test, source.tid + fn(ortbRequest, bidderRequest) { + Object.assign(ortbRequest, { + id: ortbRequest.id || bidderRequest.auctionId, + test: ortbRequest.test || 0 + }); + const timeout = parseInt(bidderRequest.timeout, 10); + if (!isNaN(timeout)) { + ortbRequest.tmax = timeout; + } + deepSetValue(ortbRequest, 'source.tid', ortbRequest.source?.tid || bidderRequest.auctionId); + } + } + }, + [IMP]: { + fpd: { + // sets initial imp to bidRequest.ortb2Imp + priority: 99, + fn(imp, bidRequest) { + mergeDeep(imp, bidRequest.ortb2Imp); + } + }, + id: { + // sets imp.id + fn(imp, bidRequest) { + imp.id = bidRequest.bidId; + } + }, + banner: { + // populates imp.banner + fn: fillBannerImp + }, + video: { + // populates imp.video + fn: fillVideoImp + }, + pbadslot: { + // removes imp.ext.data.pbaslot if it's not a string + // TODO: is this needed? + fn(imp) { + const pbadslot = imp.ext?.data?.pbadslot; + if (!pbadslot || typeof pbadslot !== 'string') { + delete imp.ext?.data?.pbadslot; + } + } + } + }, + [BID_RESPONSE]: { + mediaType: { + // sets bidResponse.mediaType from context.mediaType, falling back to seatbid.bid[].mtype + priority: 99, + fn: setResponseMediaType + }, + banner: { + // sets banner response attributes if bidResponse.mediaType === BANNER + fn: bannerResponseProcessor(), + }, + video: { + // sets video response attributes if bidResponse.mediaType === VIDEO + fn: fillVideoResponse + }, + props: { + // sets base bidResponse properties common to all types of bids + fn(bidResponse, bid, context) { + Object.entries({ + requestId: context.bidRequest?.bidId, + seatBidId: bid.id, + cpm: bid.price, + currency: context.ortbResponse.cur || context.currency, + width: bid.w, + height: bid.h, + dealId: bid.dealid, + creative_id: bid.crid, + creativeId: bid.crid, + burl: bid.burl, + ttl: bid.exp || context.ttl, + netRevenue: context.netRevenue, + }).filter(([k, v]) => typeof v !== 'undefined') + .forEach(([k, v]) => bidResponse[k] = v); + if (!bidResponse.meta) { + bidResponse.meta = {}; + } + if (bid.adomain) { + bidResponse.meta.advertiserDomains = bid.adomain; + } + } + } + } +} + +if (FEATURES.NATIVE) { + DEFAULT_PROCESSORS[IMP].native = { + // populates imp.native + fn: fillNativeImp + } + DEFAULT_PROCESSORS[BID_RESPONSE].native = { + // populates bidResponse.native if bidResponse.mediaType === NATIVE + fn: fillNativeResponse + } +} + +function fpdFromTopLevelConfig(prop) { + return { + priority: 90, // after FPD from 'ortb2', before the rest + fn(ortbRequest) { + const data = config.getConfig(prop); + if (typeof data === 'object') { + ortbRequest[prop] = mergeDeep({}, ortbRequest[prop], data); + } + } + } +} + +export function onlyOneClientSection(ortbRequest) { + ['dooh', 'app', 'site'].reduce((found, section) => { + if (ortbRequest[section] != null && Object.keys(ortbRequest[section]).length > 0) { + if (found != null) { + logWarn(`ORTB request specifies both '${found}' and '${section}'; dropping the latter.`) + delete ortbRequest[section]; + } else { + found = section; + } + } + return found; + }, null); +} diff --git a/libraries/ortbConverter/processors/mediaType.js b/libraries/ortbConverter/processors/mediaType.js new file mode 100644 index 00000000000..67232b3ca44 --- /dev/null +++ b/libraries/ortbConverter/processors/mediaType.js @@ -0,0 +1,21 @@ +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; + +export const ORTB_MTYPES = { + 1: BANNER, + 2: VIDEO, + 4: NATIVE +} + +/** + * Sets response mediaType, using ORTB 2.6 `seatbid.bid[].mtype`. + * + * Note that this will throw away bids if there is no `mtype` in the response. + */ +export function setResponseMediaType(bidResponse, bid, context) { + if (bidResponse.mediaType) return; + const mediaType = context.mediaType; + if (!mediaType && !ORTB_MTYPES.hasOwnProperty(bid.mtype)) { + throw new Error('Cannot determine mediaType for response') + } + bidResponse.mediaType = mediaType || ORTB_MTYPES[bid.mtype]; +} diff --git a/libraries/ortbConverter/processors/native.js b/libraries/ortbConverter/processors/native.js new file mode 100644 index 00000000000..ff231ce2b55 --- /dev/null +++ b/libraries/ortbConverter/processors/native.js @@ -0,0 +1,37 @@ +import {isPlainObject, logWarn, mergeDeep} from '../../../src/utils.js'; +import {NATIVE} from '../../../src/mediaTypes.js'; + +export function fillNativeImp(imp, bidRequest, context) { + if (context.mediaType && context.mediaType !== NATIVE) return; + let nativeReq = bidRequest.nativeOrtbRequest; + if (nativeReq) { + nativeReq = Object.assign({}, context.nativeRequest, nativeReq); + if (nativeReq.assets?.length) { + imp.native = mergeDeep({}, { + request: JSON.stringify(nativeReq), + ver: nativeReq.ver + }, imp.native) + } else { + logWarn('mediaTypes.native is set, but no assets were specified. Native request skipped.', bidRequest) + } + } +} + +export function fillNativeResponse(bidResponse, bid) { + if (bidResponse.mediaType === NATIVE) { + let ortb; + if (typeof bid.adm === 'string') { + ortb = JSON.parse(bid.adm); + } else { + ortb = bid.adm; + } + + if (isPlainObject(ortb) && Array.isArray(ortb.assets)) { + bidResponse.native = { + ortb, + } + } else { + throw new Error('ORTB native response contained no assets'); + } + } +} diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js new file mode 100644 index 00000000000..5fe411e6bcf --- /dev/null +++ b/libraries/ortbConverter/processors/video.js @@ -0,0 +1,66 @@ +import {deepAccess, isEmpty, logWarn, mergeDeep} from '../../../src/utils.js'; +import {VIDEO} from '../../../src/mediaTypes.js'; +import {sizesToFormat} from '../lib/sizes.js'; + +// parameters that share the same name & semantics between pbjs adUnits and imp.video +const ORTB_VIDEO_PARAMS = new Set([ + 'pos', + 'placement', + 'api', + 'mimes', + 'protocols', + 'playbackmethod', + 'minduration', + 'maxduration', + 'w', + 'h', + 'startdelay', + 'placement', + 'linearity', + 'skip', + 'skipmin', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackend' +]); + +const PLACEMENT = { + 'instream': 1, +} + +export function fillVideoImp(imp, bidRequest, context) { + if (context.mediaType && context.mediaType !== VIDEO) return; + + const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); + if (!isEmpty(videoParams)) { + const video = Object.fromEntries( + Object.entries(videoParams) + .filter(([name]) => ORTB_VIDEO_PARAMS.has(name)) + ); + if (videoParams.playerSize) { + const format = sizesToFormat(videoParams.playerSize); + if (format.length > 1) { + logWarn('video request specifies more than one playerSize; all but the first will be ignored') + } + Object.assign(video, format[0]); + } + const placement = PLACEMENT[videoParams.context]; + if (placement != null) { + video.placement = placement; + } + imp.video = mergeDeep(video, imp.video); + } +} + +export function fillVideoResponse(bidResponse, seatbid, context) { + if (bidResponse.mediaType === VIDEO) { + if (deepAccess(context.imp, 'video.w') && deepAccess(context.imp, 'video.h')) { + [bidResponse.playerWidth, bidResponse.playerHeight] = [context.imp.video.w, context.imp.video.h]; + } + + if (seatbid.adm) { bidResponse.vastXml = seatbid.adm; } + if (seatbid.nurl) { bidResponse.vastUrl = seatbid.nurl; } + } +} diff --git a/libraries/pbsExtensions/pbsExtensions.js b/libraries/pbsExtensions/pbsExtensions.js new file mode 100644 index 00000000000..1efded6173f --- /dev/null +++ b/libraries/pbsExtensions/pbsExtensions.js @@ -0,0 +1,12 @@ +import {mergeProcessors} from '../ortbConverter/lib/mergeProcessors.js'; +import {PBS_PROCESSORS} from './processors/pbs.js'; +import {getProcessors, PBS} from '../../src/pbjsORTB.js'; +import {defaultProcessors} from '../ortbConverter/converter.js'; +import {memoize} from '../../src/utils.js'; + +/** + * ORTB converter processor set that understands Prebid Server extensions. + * + * Pass this as the `processors` option to `ortbConverter` if your backend is a PBS instance. + */ +export const pbsExtensions = memoize(() => mergeProcessors(defaultProcessors(), PBS_PROCESSORS, getProcessors(PBS))); diff --git a/libraries/pbsExtensions/processors/adUnitCode.js b/libraries/pbsExtensions/processors/adUnitCode.js new file mode 100644 index 00000000000..f936e0f662f --- /dev/null +++ b/libraries/pbsExtensions/processors/adUnitCode.js @@ -0,0 +1,13 @@ +import {deepSetValue} from '../../../src/utils.js'; + +export function setImpAdUnitCode(imp, bidRequest) { + const adUnitCode = bidRequest.adUnitCode; + + if (adUnitCode) { + deepSetValue( + imp, + `ext.prebid.adunitcode`, + adUnitCode + ); + } +} diff --git a/libraries/pbsExtensions/processors/aliases.js b/libraries/pbsExtensions/processors/aliases.js new file mode 100644 index 00000000000..3dcd2c4fd9b --- /dev/null +++ b/libraries/pbsExtensions/processors/aliases.js @@ -0,0 +1,17 @@ +import adapterManager from '../../../src/adapterManager.js'; +import {deepSetValue} from '../../../src/utils.js'; + +export function setRequestExtPrebidAliases(ortbRequest, bidderRequest, context, {am = adapterManager} = {}) { + if (am.aliasRegistry[bidderRequest.bidderCode]) { + const bidder = am.bidderRegistry[bidderRequest.bidderCode]; + // adding alias only if alias source bidder exists and alias isn't configured to be standalone + // pbs adapter + if (!bidder || !bidder.getSpec().skipPbsAliasing) { + deepSetValue( + ortbRequest, + `ext.prebid.aliases.${bidderRequest.bidderCode}`, + am.aliasRegistry[bidderRequest.bidderCode] + ); + } + } +} diff --git a/libraries/pbsExtensions/processors/mediaType.js b/libraries/pbsExtensions/processors/mediaType.js new file mode 100644 index 00000000000..cbcf9a013b1 --- /dev/null +++ b/libraries/pbsExtensions/processors/mediaType.js @@ -0,0 +1,23 @@ +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; +import {ORTB_MTYPES} from '../../ortbConverter/processors/mediaType.js'; + +export const SUPPORTED_MEDIA_TYPES = { + // map from pbjs mediaType to its corresponding imp property + [BANNER]: 'banner', + [NATIVE]: 'native', + [VIDEO]: 'video' +} + +/** + * Sets bidResponse.mediaType, using ORTB 2.6 `seatbid.bid[].mtype`, falling back to `ext.prebid.type`, falling back to 'banner'. + */ +export function extPrebidMediaType(bidResponse, bid, context) { + let mediaType = context.mediaType; + if (!mediaType) { + mediaType = ORTB_MTYPES.hasOwnProperty(bid.mtype) ? ORTB_MTYPES[bid.mtype] : bid.ext?.prebid?.type + if (!SUPPORTED_MEDIA_TYPES.hasOwnProperty(mediaType)) { + mediaType = BANNER; + } + } + bidResponse.mediaType = mediaType; +} diff --git a/libraries/pbsExtensions/processors/params.js b/libraries/pbsExtensions/processors/params.js new file mode 100644 index 00000000000..010ffa5b372 --- /dev/null +++ b/libraries/pbsExtensions/processors/params.js @@ -0,0 +1,22 @@ +import {auctionManager} from '../../../src/auctionManager.js'; +import adapterManager from '../../../src/adapterManager.js'; +import {deepSetValue} from '../../../src/utils.js'; + +export function setImpBidParams( + imp, bidRequest, context, + {adUnit, bidderRequests, index = auctionManager.index, bidderRegistry = adapterManager.bidderRegistry} = {}) { + let params = bidRequest.params; + const adapter = bidderRegistry[bidRequest.bidder]; + if (adapter && adapter.getSpec().transformBidParams) { + adUnit = adUnit || index.getAdUnit(bidRequest); + bidderRequests = bidderRequests || [context.bidderRequest]; + params = adapter.getSpec().transformBidParams(params, true, adUnit, bidderRequests); + } + if (params) { + deepSetValue( + imp, + `ext.prebid.bidder.${bidRequest.bidder}`, + params + ); + } +} diff --git a/libraries/pbsExtensions/processors/pbs.js b/libraries/pbsExtensions/processors/pbs.js new file mode 100644 index 00000000000..0ed2d12fad8 --- /dev/null +++ b/libraries/pbsExtensions/processors/pbs.js @@ -0,0 +1,104 @@ +import {BID_RESPONSE, IMP, REQUEST, RESPONSE} from '../../../src/pbjsORTB.js'; +import {deepAccess, isPlainObject, isStr, mergeDeep} from '../../../src/utils.js'; +import {extPrebidMediaType} from './mediaType.js'; +import {setRequestExtPrebidAliases} from './aliases.js'; +import {setImpBidParams} from './params.js'; +import {setImpAdUnitCode} from './adUnitCode.js'; +import {setRequestExtPrebid, setRequestExtPrebidChannel} from './requestExtPrebid.js'; +import {setBidResponseVideoCache} from './video.js'; + +export const PBS_PROCESSORS = { + [REQUEST]: { + extPrebid: { + // set request.ext.prebid.auctiontimestamp, .debug and .targeting + fn: setRequestExtPrebid + }, + extPrebidChannel: { + // sets request.ext.prebid.channel + fn: setRequestExtPrebidChannel + }, + extPrebidAliases: { + // sets ext.prebid.aliases + fn: setRequestExtPrebidAliases + } + }, + [IMP]: { + params: { + // sets bid ext.prebid.bidder.[bidderCode] with bidRequest.params, passed through transformBidParams if necessary + fn: setImpBidParams + }, + adUnitCode: { + // sets bid ext.prebid.adunitcode + fn: setImpAdUnitCode + } + }, + [BID_RESPONSE]: { + mediaType: { + // sets bidResponse.mediaType according to context.mediaType, falling back to imp.ext.prebid.type + fn: extPrebidMediaType, + priority: 99, + }, + videoCache: { + // sets response video attributes; in addition, looks at ext.prebid.cache and .targeting to set video cache key and URL + fn: setBidResponseVideoCache, + priority: -10, // after 'video' + }, + bidderCode: { + // sets bidderCode from on seatbid.seat + fn(bidResponse, bid, context) { + bidResponse.bidderCode = context.seatbid.seat; + bidResponse.adapterCode = deepAccess(bid, 'ext.prebid.meta.adaptercode') || context.bidRequest?.bidder || bidResponse.bidderCode; + } + }, + pbsBidId: { + // sets bidResponse.pbsBidId from ext.prebid.bidid + fn(bidResponse, bid) { + const bidId = deepAccess(bid, 'ext.prebid.bidid'); + if (isStr(bidId)) { + bidResponse.pbsBidId = bidId; + } + } + }, + adserverTargeting: { + // sets bidResponse.adserverTargeting from ext.prebid.targeting + fn(bidResponse, bid) { + const targeting = deepAccess(bid, 'ext.prebid.targeting'); + if (isPlainObject(targeting)) { + bidResponse.adserverTargeting = targeting; + } + } + }, + extPrebidMeta: { + // sets bidResponse.meta from ext.prebid.meta + fn(bidResponse, bid) { + bidResponse.meta = mergeDeep({}, deepAccess(bid, 'ext.prebid.meta'), bidResponse.meta); + } + }, + pbsWurl: { + // sets bidResponse.pbsWurl from ext.prebid.events.win + fn(bidResponse, bid) { + const wurl = deepAccess(bid, 'ext.prebid.events.win'); + if (isStr(wurl)) { + bidResponse.pbsWurl = wurl; + } + } + }, + }, + [RESPONSE]: { + serverSideStats: { + // updates bidderRequest and bidRequests with serverErrors from ext.errors and serverResponseTimeMs from ext.responsetimemillis + fn(response, ortbResponse, context) { + Object.entries({ + errors: 'serverErrors', + responsetimemillis: 'serverResponseTimeMs' + }).forEach(([serverName, clientName]) => { + const value = deepAccess(ortbResponse, `ext.${serverName}.${context.bidderRequest.bidderCode}`); + if (value) { + context.bidderRequest[clientName] = value; + context.bidRequests.forEach(bid => bid[clientName] = value); + } + }) + } + }, + } +} diff --git a/libraries/pbsExtensions/processors/requestExtPrebid.js b/libraries/pbsExtensions/processors/requestExtPrebid.js new file mode 100644 index 00000000000..bbb6add45ce --- /dev/null +++ b/libraries/pbsExtensions/processors/requestExtPrebid.js @@ -0,0 +1,30 @@ +import {deepSetValue, mergeDeep} from '../../../src/utils.js'; +import {config} from '../../../src/config.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; + +export function setRequestExtPrebid(ortbRequest, bidderRequest) { + deepSetValue( + ortbRequest, + 'ext.prebid', + mergeDeep( + { + auctiontimestamp: bidderRequest.auctionStart, + targeting: { + includewinners: true, + includebidderkeys: false + } + }, + ortbRequest.ext?.prebid, + ) + ); + if (config.getConfig('debug')) { + ortbRequest.ext.prebid.debug = true; + } +} + +export function setRequestExtPrebidChannel(ortbRequest) { + deepSetValue(ortbRequest, 'ext.prebid.channel', Object.assign({ + name: 'pbjs', + version: getGlobal().version + }, ortbRequest.ext?.prebid?.channel)); +} diff --git a/libraries/pbsExtensions/processors/video.js b/libraries/pbsExtensions/processors/video.js new file mode 100644 index 00000000000..0a517fd0575 --- /dev/null +++ b/libraries/pbsExtensions/processors/video.js @@ -0,0 +1,23 @@ +import {VIDEO} from '../../../src/mediaTypes.js'; +import {deepAccess} from '../../../src/utils.js'; + +export function setBidResponseVideoCache(bidResponse, bid) { + if (bidResponse.mediaType === VIDEO) { + // try to get cache values from 'response.ext.prebid.cache' + // else try 'bid.ext.prebid.targeting' as fallback + let {cacheId: videoCacheKey, url: vastUrl} = deepAccess(bid, 'ext.prebid.cache.vastXml') || {}; + if (!videoCacheKey || !vastUrl) { + const {hb_uuid: uuid, hb_cache_host: cacheHost, hb_cache_path: cachePath} = deepAccess(bid, 'ext.prebid.targeting') || {}; + if (uuid && cacheHost && cachePath) { + videoCacheKey = uuid; + vastUrl = `https://${cacheHost}${cachePath}?uuid=${uuid}`; + } + } + if (videoCacheKey && vastUrl) { + Object.assign(bidResponse, { + videoCacheKey, + vastUrl + }) + } + } +} diff --git a/libraries/video/constants/constants.js b/libraries/video/constants/constants.js new file mode 100644 index 00000000000..55e3785ccc2 --- /dev/null +++ b/libraries/video/constants/constants.js @@ -0,0 +1,7 @@ +export const videoKey = 'video'; + +export const PLAYBACK_MODE = { + VOD: 0, + LIVE: 1, + DVR: 2 +}; diff --git a/libraries/video/constants/events.js b/libraries/video/constants/events.js new file mode 100644 index 00000000000..b7932adf621 --- /dev/null +++ b/libraries/video/constants/events.js @@ -0,0 +1,98 @@ +// Life Cycle +export const SETUP_COMPLETE = 'setupComplete'; +export const SETUP_FAILED = 'setupFailed'; +export const DESTROYED = 'destroyed'; + +// Ads +export const AD_REQUEST = 'adRequest'; +export const AD_BREAK_START = 'adBreakStart'; +export const AD_LOADED = 'adLoaded'; +export const AD_STARTED = 'adStarted'; +export const AD_IMPRESSION = 'adImpression'; +export const AD_PLAY = 'adPlay'; +export const AD_TIME = 'adTime'; +export const AD_PAUSE = 'adPause'; +export const AD_CLICK = 'adClick'; +export const AD_SKIPPED = 'adSkipped'; +export const AD_ERROR = 'adError'; +export const AD_COMPLETE = 'adComplete'; +export const AD_BREAK_END = 'adBreakEnd'; + +// Media +export const PLAYLIST = 'playlist'; +export const PLAYBACK_REQUEST = 'playbackRequest'; +export const AUTOSTART_BLOCKED = 'autostartBlocked'; +export const PLAY_ATTEMPT_FAILED = 'playAttemptFailed'; +export const CONTENT_LOADED = 'contentLoaded'; +export const PLAY = 'play'; +export const PAUSE = 'pause'; +export const BUFFER = 'buffer'; +export const TIME = 'time'; +export const SEEK_START = 'seekStart'; +export const SEEK_END = 'seekEnd'; +export const MUTE = 'mute'; +export const VOLUME = 'volume'; +export const RENDITION_UPDATE = 'renditionUpdate'; +export const ERROR = 'error'; +export const COMPLETE = 'complete'; +export const PLAYLIST_COMPLETE = 'playlistComplete'; + +// Layout +export const FULLSCREEN = 'fullscreen'; +export const PLAYER_RESIZE = 'playerResize'; +export const VIEWABLE = 'viewable'; +export const CAST = 'cast'; + +export const allVideoEvents = [ + SETUP_COMPLETE, SETUP_FAILED, DESTROYED, AD_REQUEST, AD_BREAK_START, AD_LOADED, AD_STARTED, + AD_IMPRESSION, AD_PLAY, AD_TIME, AD_PAUSE, AD_CLICK, AD_SKIPPED, AD_ERROR, AD_COMPLETE, AD_BREAK_END, PLAYLIST, + PLAYBACK_REQUEST, AUTOSTART_BLOCKED, PLAY_ATTEMPT_FAILED, CONTENT_LOADED, PLAY, PAUSE, BUFFER, TIME, SEEK_START, + SEEK_END, MUTE, VOLUME, RENDITION_UPDATE, ERROR, COMPLETE, PLAYLIST_COMPLETE, FULLSCREEN, PLAYER_RESIZE, VIEWABLE, + CAST +]; + +export const AUCTION_AD_LOAD_ATTEMPT = 'auctionAdLoadAttempt'; +export const AUCTION_AD_LOAD_QUEUED = 'auctionAdLoadQueued'; +export const AUCTION_AD_LOAD_ABORT = 'auctionAdLoadAbort'; +export const BID_IMPRESSION = 'bidImpression'; +export const BID_ERROR = 'bidError'; + +export const videoEvents = { + SETUP_COMPLETE, + SETUP_FAILED, + DESTROYED, + AD_REQUEST, + AD_BREAK_START, + AD_LOADED, + AD_STARTED, + AD_IMPRESSION, + AD_PLAY, + AD_TIME, + AD_PAUSE, + AD_CLICK, + AD_SKIPPED, + AD_ERROR, + AD_COMPLETE, + AD_BREAK_END, + PLAYLIST, + PLAYBACK_REQUEST, + AUTOSTART_BLOCKED, + PLAY_ATTEMPT_FAILED, + CONTENT_LOADED, + PLAY, + PAUSE, + BUFFER, + TIME, + SEEK_START, + SEEK_END, + MUTE, + VOLUME, + RENDITION_UPDATE, + ERROR, + COMPLETE, + PLAYLIST_COMPLETE, + FULLSCREEN, + PLAYER_RESIZE, + VIEWABLE, + CAST, +}; diff --git a/libraries/video/constants/ortb.js b/libraries/video/constants/ortb.js new file mode 100644 index 00000000000..d67c8a5f393 --- /dev/null +++ b/libraries/video/constants/ortb.js @@ -0,0 +1,169 @@ +/** + * @typedef {Object} OrtbParams + * @property {OrtbVideoParamst} video + * @property {OrtbContentParams} content + */ + +/** + * @typedef OrtbVideoParams + * @property {[string]} mimes - Content MIME types supported (e.g., “video/x-ms-wmv”, “video/mp4”). + * @property {number|undefined} minduration - Minimum video ad duration in seconds. + * @property {number|undefined} maxduration - Maximum video ad duration in seconds. + * @property {[number]} protocols - Supported video protocols. At least one supported protocol must be specified. + * @property {number} w - Width of the video player in device independent pixels (DIPS). + * @property {number} h - Height of the video player in device independent pixels (DIPS). + * @property {number|undefined} startdelay - Indicates the offset of the ad placement. + * @property {number|undefined} placement - Placement type for the impression. + * @property {number|undefined} linearity - Indicates if the impression must be linear, nonlinear, etc. If omitted, assume all are allowed. + * @property {number} skip - Indicates if the player can allow the video to be skipped, where 0 is no, 1 is yes. + * @property {number|undefined} skipmin - Only ad creatives with a duration greater than this value can be skippable; only applicable if the ad is skippable. + * @property {number|undefined} skipafter - Number of seconds a video must play before skipping is enabled; only applicable if the ad is skippable. + * @property {number|undefined} sequence - If multiple ad impressions are offered in the same bid request, the sequence number will allow for the coordinated delivery of multiple creatives. + * @property {[number]|undefined} battr - Blocked creative attributes. Use this to indicate which creatives the player does not support. + * @property {number|undefined} maxextended - Maximum extended ad duration if extension is allowed. + * @property {number|undefined} minbitrate - Minimum bit rate in Kbps supported by the player. + * @property {number|undefined} maxbitrate - Maximum bit rate in Kbps supported by the player. + * @property {number|undefined} boxingallowed - Indicates if letter-boxing of 4:3 content into a 16:9 window is allowed. 0 is no, 1 is yes. + * @property {[number]|undefined} playbackmethod - Playback methods that may be in use. + * @property {number|undefined} playbackend - The scenario that causes playback to end. + * @property {[number]|undefined} delivery - Supported delivery methods (e.g., streaming, progressive). + * @property {number|undefined} pos - Ad position on screen. + * @property {[Object]|undefined} companionad - list of companion ads. Refer to Section 3.2.6 of the oRTB v2.5 spec for the interface of the companion ad object. + * @property {[number]|undefined} api - List of supported API frameworks for this impression. + * @property {[number]|undefined} companiontype - Supported VAST companion ad types. Refer to List 5.14 of the oRTB v2.5 spec for the interface. + * @property {Object|undefined} ext - Placeholder for exchange-specific extensions to OpenRTB. + */ + +/** + * @typedef OrtbContentParams + * @property {string} id - ID uniquely identifying the content. + * @property {string} url - URL of the content, for buy-side contextualization or review. + * @property {number|undefined} episode - Episode number. + * @property {string|undefined} title - Content title. + * @property {string|undefined} series - Content series i.e. “The Office” (television), “Star Wars” (movie). + * @property {string|undefined} season - Content season (e.g., “Season 3”). + * @property {string|undefined} artist - Artist credited with the content. + * @property {string|undefined} genre - Genre that best describes the content (e.g., rock, pop, etc). + * @property {string|undefined} album - Album to which the content belongs. Typically for audio. + * @property {string|undefined} isrc - International Standard Recording Code conforming to ISO3901. + * @property {Object|undefined} producer - Details about the content Producer. For Producer interface visit Section 3.2.17 of the oRTB v2.5 spec. + * @property {[string]|undefined} cat - List of IAB content categories that describe the content. Refer to List 5.1. of the oRTB v2.5 spec for the complete list. + * @property {number|undefined} prodq - Production quality. Refer to List 5.13 of the oRTB v2.5 spec. + * @property {number|undefined} context - Type of content (game, video, text, etc.). Refer to List 5.18 of the oRTB v2.5 spec. + * @property {string|undefined} contentrating - Content rating (e.g., MPAA). + * @property {string|undefined} userrating - User rating of the content (e.g., number of stars, likes, etc.). + * @property {number|undefined} qagmediarating - Media rating per IQG guidelines. Refer to List 5.19 of the oRTB v2.5 spec. + * @property {string|undefined} keywords - Comma separated list of keywords describing the content. + * @property {number|undefined} livestream - Whether the stream is live or not. 0 means not live (VOD), 1 means content is live streaming. + * @property {number|undefined} sourcerelationship - 0 means indirect, 1 means direct. + * @property {number} len - Duration of content in seconds. + * @property {string|undefined} language - Content language using ISO-639-1-alpha-2. + * @property {number|undefined} embeddable - Indicator of whether or not the content is embeddable (e.g., an embeddable video player). 0 means no, 1 means yes. + * @property {[Object]|undefined} data - Additional content data. Each Data object represents a different data source. See Section 3.2.21 of the oRTB v2.5 spec. + * @property {Object|undefined} ext - Placeholder for exchange-specific extensions to OpenRTB. + */ + +const VIDEO_PREFIX = 'video/'; +const APPLICATION_PREFIX = 'application/'; + +/** + * ORTB 2.5 section 3.2.7 - Video.mimes + * @enum OrtbVideoParams.mimes + */ +export const VIDEO_MIME_TYPE = { + MP4: VIDEO_PREFIX + 'mp4', + MPEG: VIDEO_PREFIX + 'mpeg', + OGG: VIDEO_PREFIX + 'ogg', + WEBM: VIDEO_PREFIX + 'webm', + AAC: VIDEO_PREFIX + 'aac', + HLS: APPLICATION_PREFIX + 'vnd.apple.mpegurl' +}; + +export const JS_APP_MIME_TYPE = APPLICATION_PREFIX + 'javascript'; +export const VPAID_MIME_TYPE = JS_APP_MIME_TYPE; + +/** + * ORTB 2.5 section 5.9 - Video Placement Types + * @enum OrtbVideoParams.placement + */ +export const PLACEMENT = { + INSTREAM: 1, + BANNER: 2, + ARTICLE: 3, + FEED: 4, + INTERSTITIAL: 5, + SLIDER: 5, + FLOATING: 5, + INTERSTITIAL_SLIDER_FLOATING: 5 +}; + +/** + * ORTB 2.5 section 5.4 - Ad Position + * @enum OrtbVideoParams.pos + */ +export const AD_POSITION = { + UNKNOWN: 0, + ABOVE_THE_FOLD: 1, + BELOW_THE_FOLD: 3, + HEADER: 4, + FOOTER: 5, + SIDEBAR: 6, + FULL_SCREEN: 7 +} + +/** + * ORTB 2.5 section 5.11 - Playback Cessation Modes + * @enum OrtbVideoParams.playbackend + */ +export const PLAYBACK_END = { + VIDEO_COMPLETION: 1, + VIEWPORT_LEAVE: 2, + FLOATING: 3 +} + +/** + * ORTB 2.5 section 5.10 - Playback Methods + * @enum OrtbVideoParams.playbackmethod + */ +export const PLAYBACK_METHODS = { + AUTOPLAY: 1, + AUTOPLAY_MUTED: 2, + CLICK_TO_PLAY: 3, + CLICK_TO_PLAY_MUTED: 4, + VIEWABLE: 5, + VIEWABLE_MUTED: 6 +}; + +/** + * ORTB 2.5 section 5.8 - Protocols + * @enum OrtbVideoParams.protocols + */ +export const PROTOCOLS = { + // VAST_1_0: 1, + VAST_2_0: 2, + VAST_3_0: 3, + // VAST_1_O_WRAPPER: 4, + VAST_2_0_WRAPPER: 5, + VAST_3_0_WRAPPER: 6, + VAST_4_0: 7, + VAST_4_0_WRAPPER: 8 +}; + +/** + * ORTB 2.5 section 5.6 - API Frameworks + * @enum OrtbVideoParams.api + */ +export const API_FRAMEWORKS = { + VPAID_1_0: 1, + VPAID_2_0: 2, + OMID_1_0: 7 +}; + +/** + * ORTB 2.5 section 5.18 - Content Context + * @enum OrtbContentParams.context + */ +export const CONTEXT = { + VIDEO: 1, + AUDIO: 3 +}; diff --git a/libraries/video/constants/vendorCodes.js b/libraries/video/constants/vendorCodes.js new file mode 100644 index 00000000000..370c151b997 --- /dev/null +++ b/libraries/video/constants/vendorCodes.js @@ -0,0 +1,6 @@ +// Video Vendors +export const JWPLAYER_VENDOR = 1; +export const VIDEO_JS_VENDOR = 2; + +// Ad Server Vendors +export const GAM_VENDOR = 'gam'; diff --git a/libraries/video/shared/eventHandler.js b/libraries/video/shared/eventHandler.js new file mode 100644 index 00000000000..ee0d6b2cbfb --- /dev/null +++ b/libraries/video/shared/eventHandler.js @@ -0,0 +1,18 @@ +/** + * Builds a standard event handler + * @param {String} type Event name + * @param {(function(String, Object): Object)} callback Callback defined by publisher to be executed when the event occurs + * @param {Object} payload Base payload defined when the event is registered + * @param {(function(*): Object)|null|undefined} getExtraPayload Parses the player's event payload to return a normalized payload + * @returns {(function(*): void)|*} event handler + */ +export function getEventHandler(type, callback, payload, getExtraPayload) { + return event => { + if (getExtraPayload) { + const extraPayload = getExtraPayload(event); + Object.assign(payload, extraPayload); + } + + callback(type, payload); + }; +} diff --git a/libraries/video/shared/helpers.js b/libraries/video/shared/helpers.js new file mode 100644 index 00000000000..1b87f3bf1f3 --- /dev/null +++ b/libraries/video/shared/helpers.js @@ -0,0 +1,5 @@ +import { videoKey } from '../constants/constants.js' + +export function getExternalVideoEventName(eventName) { + return videoKey + eventName.replace(/^./, eventName[0].toUpperCase()); +} diff --git a/libraries/video/shared/parentModule.js b/libraries/video/shared/parentModule.js new file mode 100644 index 00000000000..06c71ebd75b --- /dev/null +++ b/libraries/video/shared/parentModule.js @@ -0,0 +1,81 @@ +/** + * @typedef {Object} ParentModule + * @summary abstraction for any module to store and reference its submodules + * @param {SubmoduleBuilder} submoduleBuilder_ + * @returns {ParentModule} + * @constructor + */ +export function ParentModule(submoduleBuilder_) { + const submoduleBuilder = submoduleBuilder_; + const submodules = {}; + + /** + * @function ParentModule#registerSubmodule + * @summary Stores a submodule + * @param {String} id - unique identifier of the submodule instance + * @param {String} vendorCode - identifier to the submodule type that must be built + * @param {Object} config - additional information necessary to instantiate the submodule + */ + function registerSubmodule(id, vendorCode, config) { + if (submodules[id]) { + return; + } + + let submodule; + try { + submodule = submoduleBuilder.build(vendorCode, config); + } catch (e) { + throw e; + } + submodules[id] = submodule; + } + + /** + * @function ParentModule#getSubmodule + * @summary Stores a submodule + * @param {String} id - unique identifier of the submodule instance + * @returns {Object} - a submodule instance + */ + function getSubmodule(id) { + return submodules[id]; + } + + return { + registerSubmodule, + getSubmodule + } +} + +/** + * @typedef {Object} SubmoduleBuilder + * @summary Instantiates submodules + * @param {vendorSubmoduleDirectory} submoduleDirectory_ + * @param {Object|null|undefined} sharedUtils_ + * @returns {SubmoduleBuilder} + * @constructor + */ +export function SubmoduleBuilder(submoduleDirectory_, sharedUtils_) { + const submoduleDirectory = submoduleDirectory_; + const sharedUtils = sharedUtils_; + + /** + * @function SubmoduleBuilder#build + * @param vendorCode - identifier to the submodule type that must be instantiated + * @param config - additional information necessary to instantiate the submodule + * @throws + * @returns {{init}|*} - a submodule instance + */ + function build(vendorCode, config) { + const submoduleFactory = submoduleDirectory[vendorCode]; + if (!submoduleFactory) { + throw new Error('Unrecognized submodule vendor code: ' + vendorCode); + } + + const submodule = submoduleFactory(config, sharedUtils); + return submodule; + } + + return { + build + }; +} diff --git a/libraries/video/shared/state.js b/libraries/video/shared/state.js new file mode 100644 index 00000000000..b84cff8f997 --- /dev/null +++ b/libraries/video/shared/state.js @@ -0,0 +1,47 @@ +/** + * @typedef {Object} State + * @summary simple state object. Can be subclassed + * @function updateState + * @function getState + * @function clearState + */ + +/** + * @summary factory to create a simple state object + * @returns {State} + */ +export default function stateFactory() { + let state = {}; + + /** + * @function State#updateState + * @summary updates the state + * @param {Object} stateUpdate + */ + function updateState(stateUpdate) { + Object.assign(state, stateUpdate); + } + + /** + * @function State#getState + * @summary provides the current state + * @returns {Object} the current state + */ + function getState() { + return state; + } + + /** + * @function State#clearState + * @summary erases the current state + */ + function clearState() { + state = {}; + } + + return { + updateState, + getState, + clearState + }; +} diff --git a/libraries/video/shared/vastXmlBuilder.js b/libraries/video/shared/vastXmlBuilder.js new file mode 100644 index 00000000000..35547acc479 --- /dev/null +++ b/libraries/video/shared/vastXmlBuilder.js @@ -0,0 +1,77 @@ +import { getGlobal } from '../../../src/prebidGlobal.js'; + +export function buildVastWrapper(adId, adTagUrl, impressionUrl, impressionId, errorUrl) { + let wrapperBody = getAdSystemNode('Prebid org', getGlobal().version); + + if (adTagUrl) { + wrapperBody += getAdTagUriNode(adTagUrl); + } + + if (impressionUrl) { + wrapperBody += getImpressionNode(impressionUrl, impressionId); + } + + if (errorUrl) { + wrapperBody += getErrorNode(errorUrl); + } + + return getVastNode(getAdNode(getWrapperNode(wrapperBody), adId), '4.2'); +} + +export function getVastNode(body, vastVersion) { + return getNode('VAST', body, { version: vastVersion }); +} + +export function getAdNode(body, adId) { + return getNode('Ad', body, { id: adId }); +} + +export function getWrapperNode(body) { + return getNode('Wrapper', body); +} + +export function getAdSystemNode(system, version) { + return getNode('AdSystem', system, { version }); +} + +export function getAdTagUriNode(adTagUrl) { + return getUrlNode('VASTAdTagURI', adTagUrl); +} + +export function getImpressionNode(pingUrl, id) { + return getUrlNode('Impression', pingUrl, { id }); +} + +export function getErrorNode(pingUrl) { + return getUrlNode('Error', pingUrl); +} + +// Helpers + +function getUrlNode(labelName, url, attributes) { + const body = ``; + return getNode(labelName, body, attributes); +} + +function getNode(labelName, body, attributes) { + const openingLabel = getOpeningLabel(labelName, attributes); + return `<${openingLabel}>${body}`; +} + +/* +attributes is a KVP Object. + */ +function getOpeningLabel(name, attributes) { + if (!attributes) { + return name; + } + + return Object.keys(attributes).reduce((label, key) => { + const value = attributes[key]; + if (!value) { + return label; + } + + return label + ` ${key}="${value}"`; + }, name); +} diff --git a/libraries/video/shared/vastXmlEditor.js b/libraries/video/shared/vastXmlEditor.js new file mode 100644 index 00000000000..b586e5b4c29 --- /dev/null +++ b/libraries/video/shared/vastXmlEditor.js @@ -0,0 +1,115 @@ +import { getErrorNode, getImpressionNode, buildVastWrapper } from './vastXmlBuilder.js'; + +export const XML_MIME_TYPE = 'application/xml'; + +export function VastXmlEditor(xmlUtil_) { + const xmlUtil = xmlUtil_; + + function getVastXmlWithTracking(vastXml, adId, impressionUrl, impressionId, errorUrl) { + const impressionDoc = getImpressionDoc(impressionUrl, impressionId); + const errorDoc = getErrorDoc(errorUrl); + if (!adId && !impressionDoc && !errorDoc) { + return vastXml; + } + + const vastXmlDoc = xmlUtil.parse(vastXml); + appendTrackingNodes(vastXmlDoc, impressionDoc, errorDoc); + replaceAdId(vastXmlDoc, adId); + return xmlUtil.serialize(vastXmlDoc); + } + + function appendTrackingNodes(vastXmlDoc, impressionDoc, errorDoc) { + const nodes = vastXmlDoc.querySelectorAll('InLine,Wrapper'); + const nodeCount = nodes.length; + for (let i = 0; i < nodeCount; i++) { + const node = nodes[i]; + // A copy of the child is required until we reach the last node. + const requiresCopy = i < nodeCount - 1; + appendChild(node, impressionDoc, requiresCopy); + appendChild(node, errorDoc, requiresCopy); + } + } + + function replaceAdId(vastXmlDoc, adId) { + if (!adId) { + return; + } + + const adNode = vastXmlDoc.querySelector('Ad'); + if (!adNode) { + return; + } + + adNode.id = adId; + } + + return { + getVastXmlWithTracking, + buildVastWrapper + } + + function getImpressionDoc(impressionUrl, impressionId) { + if (!impressionUrl) { + return; + } + + const impressionNode = getImpressionNode(impressionUrl, impressionId); + return xmlUtil.parse(impressionNode); + } + + function getErrorDoc(errorUrl) { + if (!errorUrl) { + return; + } + + const errorNode = getErrorNode(errorUrl); + return xmlUtil.parse(errorNode); + } + + function appendChild(node, child, copy) { + if (!child) { + return; + } + + const doc = copy ? child.cloneNode(true) : child; + node.appendChild(doc.documentElement); + } +} + +function XMLUtil() { + let parser; + let serializer; + + function getParser() { + if (!parser) { + // DOMParser instantiation is costly; instantiate only once throughout Prebid lifecycle. + parser = new DOMParser(); + } + return parser; + } + + function getSerializer() { + if (!serializer) { + // XMLSerializer instantiation is costly; instantiate only once throughout Prebid lifecycle. + serializer = new XMLSerializer(); + } + return serializer; + } + + function parse(xmlString) { + return getParser().parseFromString(xmlString, XML_MIME_TYPE); + } + + function serialize(xmlDoc) { + return getSerializer().serializeToString(xmlDoc); + } + + return { + parse, + serialize + }; +} + +export function vastXmlEditorFactory() { + return VastXmlEditor(XMLUtil()); +} diff --git a/modules/.submodules.json b/modules/.submodules.json index d3665dd38f4..a535fd4988d 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -36,6 +36,7 @@ "quantcastIdSystem", "sharedIdSystem", "tapadIdSystem", + "teadsIdSystem", "tncIdSystem", "trustpidSystem", "uid2IdSystem", @@ -51,10 +52,14 @@ ], "rtdModule": [ "1plusXRtdProvider", + "aaxBlockmeterRtdProvider", "airgridRtdProvider", "akamaiDapRtdProvider", + "arcspanRtdProvider", "blueconicRtdProvider", "browsiRtdProvider", + "captifyRtdProvider", + "confiantRtdProvider", "dgkeywordRtdProvider", "geoedgeRtdProvider", "hadronRtdProvider", @@ -62,18 +67,23 @@ "iasRtdProvider", "jwplayerRtdProvider", "medianetRtdProvider", + "mgidRtdProvider", "oneKeyRtdProvider", "optimeraRtdProvider", "permutiveRtdProvider", "reconciliationRtdProvider", "sirdataRtdProvider", "timeoutRtdProvider", - "weboramaRtdProvider" + "weboramaRtdProvider", + "zeusPrimeRtdProvider" ], "fpdModule": [ - "enrichmentFpdModule", "validationFpdModule", "topicsFpdModule" + ], + "videoModule": [ + "jwplayerVideoProvider", + "videojsVideoProvider" ] } -} \ No newline at end of file +} diff --git a/modules/1plusXRtdProvider.js b/modules/1plusXRtdProvider.js index 317bb4a47af..390f4f4cd81 100644 --- a/modules/1plusXRtdProvider.js +++ b/modules/1plusXRtdProvider.js @@ -13,6 +13,7 @@ const MODULE_NAME = '1plusX'; const ORTB2_NAME = '1plusX.com' const PAPI_VERSION = 'v1.0'; const LOG_PREFIX = '[1plusX RTD Module]: '; +const OPE_FPID = 'ope_fpid' const LEGACY_SITE_KEYWORDS_BIDDERS = ['appnexus']; export const segtaxes = { // cf. https://github.com/InteractiveAdvertisingBureau/openrtb/pull/108 @@ -57,6 +58,12 @@ export const extractConfig = (moduleConfig, reqBidsConfigObj) => { return { customerId, timeout, bidders }; } +/** + * Extracts consent from the prebid consent object and translates it + * into a 1plusX profile api query parameter parameter dict + * @param {object} prebid gdpr object + * @returns dictionary of papi gdpr query parameters + */ export const extractConsent = ({ gdpr }) => { if (!gdpr) { return null @@ -65,8 +72,8 @@ export const extractConsent = ({ gdpr }) => { if (!(gdprApplies == '0' || gdprApplies == '1')) { throw 'TCF Consent: gdprApplies has wrong format' } - if (!(typeof consentString === 'string')) { - throw 'TCF Consent: consentString is not string' + if (consentString && typeof consentString != 'string') { + throw 'TCF Consent: consentString must be string if defined' } const result = { 'gdpr_applies': gdprApplies, @@ -74,13 +81,30 @@ export const extractConsent = ({ gdpr }) => { } return result } + +/** + * Extracts the OPE first party id field from local storage + * @returns fpid string if found, else null + */ +export const extractFpid = () => { + try { + const fpid = window.localStorage.getItem(OPE_FPID); + if (fpid) { + return fpid; + } + return null; + } catch (error) { + return null; + } +} /** * Gets the URL of Profile Api from which targeting data will be fetched - * @param {Object} config * @param {string} config.customerId + * @param {object} consent query params as dict + * @param {string} oneplusx first party id (nullable) * @returns {string} URL to access 1plusX Profile API */ -export const getPapiUrl = (customerId, consent) => { +export const getPapiUrl = (customerId, consent, fpid) => { // https://[yourClientId].profiles.tagger.opecloud.com/[VERSION]/targeting?url= const currentUrl = encodeURIComponent(window.location.href); var papiUrl = `https://${customerId}.profiles.tagger.opecloud.com/${PAPI_VERSION}/targeting?url=${currentUrl}`; @@ -89,6 +113,9 @@ export const getPapiUrl = (customerId, consent) => { papiUrl += `&${key}=${value}` }) } + if (fpid) { + papiUrl += `&fpid=${fpid}` + } return papiUrl; } @@ -246,7 +273,7 @@ const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent // Get the required config const { customerId, bidders } = extractConfig(moduleConfig, reqBidsConfigObj); // Get PAPI URL - const papiUrl = getPapiUrl(customerId, extractConsent(userConsent) || {}) + const papiUrl = getPapiUrl(customerId, extractConsent(userConsent) || {}, extractFpid()) // Call PAPI getTargetingDataFromPapi(papiUrl) .then((papiResponse) => { diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 89c00897571..e9901794ff9 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -11,6 +11,7 @@ import { logWarn, getWindowSelf, mergeDeep, + pick } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -166,7 +167,8 @@ function buildRequests(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } = _buildRequestParams(bidRequests, bidderRequest); const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); @@ -180,6 +182,7 @@ function buildRequests(bidRequests, bidderRequest) { gdprConsent, uspConsent, pageUrl, + referer, ttxSettings }) ) @@ -198,7 +201,9 @@ function _buildRequestParams(bidRequests, bidderRequest) { const uspConsent = bidderRequest && bidderRequest.uspConsent; - const pageUrl = bidderRequest?.refererInfo?.page + const pageUrl = bidderRequest?.refererInfo?.page; + + const referer = bidderRequest?.refererInfo?.ref; adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); @@ -206,7 +211,8 @@ function _buildRequestParams(bidRequests, bidderRequest) { ttxSettings, gdprConsent, uspConsent, - pageUrl + pageUrl, + referer } } @@ -240,9 +246,10 @@ function _getMRAKey(bidRequest) { } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, referer, ttxSettings }) { const ttxRequest = {}; - const { siteId, test } = bidRequests[0].params; + const firstBidRequest = bidRequests[0]; + const { siteId, test } = firstBidRequest.params; /* * Infer data for the request payload @@ -254,13 +261,17 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU }); ttxRequest.site = { id: siteId }; - ttxRequest.device = _buildDeviceORTB(); + ttxRequest.device = _buildDeviceORTB(firstBidRequest.ortb2?.device); if (pageUrl) { ttxRequest.site.page = pageUrl; } - ttxRequest.id = bidRequests[0].auctionId; + if (referer) { + ttxRequest.site.ref = referer; + } + + ttxRequest.id = firstBidRequest.auctionId; if (gdprConsent.consentString) { ttxRequest.user = setExtensions(ttxRequest.user, { @@ -268,9 +279,9 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU }); } - if (Array.isArray(bidRequests[0].userIdAsEids) && bidRequests[0].userIdAsEids.length > 0) { + if (Array.isArray(firstBidRequest.userIdAsEids) && firstBidRequest.userIdAsEids.length > 0) { ttxRequest.user = setExtensions(ttxRequest.user, { - 'eids': bidRequests[0].userIdAsEids + 'eids': firstBidRequest.userIdAsEids }); } @@ -294,9 +305,9 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU } }; - if (bidRequests[0].schain) { + if (firstBidRequest.schain) { ttxRequest.source = setExtensions(ttxRequest.source, { - 'schain': bidRequests[0].schain + 'schain': firstBidRequest.schain }); } @@ -739,10 +750,9 @@ function _createSync({ siteId = 'zzz000000000003zzz', gdprConsent = {}, uspConse } // BUILD REQUESTS: DEVICE -function _buildDeviceORTB() { +function _buildDeviceORTB(device = {}) { const win = getWindowSelf(); - - return { + const deviceProps = { ext: { ttx: { ...getScreenDimensions(), @@ -752,7 +762,13 @@ function _buildDeviceORTB() { mtp: win.navigator.maxTouchPoints } } - }; + } + + if (device.sua) { + deviceProps.sua = pick(device.sua, [ 'browsers', 'platform', 'model' ]); + } + + return deviceProps; } function getTopMostAccessibleWindow() { diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 3763fee5124..5e07fe9523d 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -13,6 +13,7 @@ import { uspDataHandler } from '../src/adapterManager.js'; const MODULE_NAME = '33acrossId'; const API_URL = 'https://lexicon.33across.com/v1/envelope'; const AJAX_TIMEOUT = 10000; +const CALLER_NAME = 'pbjs'; function getEnvelope(response) { if (!response.succeeded) { @@ -36,6 +37,8 @@ function calculateQueryStringParams(pid, gdprConsentData) { const params = { pid, gdpr: Number(gdprApplies), + src: CALLER_NAME, + ver: '$prebid.version$' }; if (uspString) { diff --git a/modules/aaxBlockmeterRtdProvider.js b/modules/aaxBlockmeterRtdProvider.js new file mode 100644 index 00000000000..a3b7b4812a7 --- /dev/null +++ b/modules/aaxBlockmeterRtdProvider.js @@ -0,0 +1,59 @@ +import {isEmptyStr, isStr, logError, isFn, logWarn} from '../src/utils.js'; +import {submodule} from '../src/hook.js'; +import { loadExternalScript } from '../src/adloader.js'; + +export const _config = { + MODULE: 'aaxBlockmeter', + ADSERVER_TARGETING_KEY: 'atk', + BLOCKMETER_URL: 'c.aaxads.com/aax.js', + VERSION: '1.2' +}; + +window.aax = window.aax || {}; + +function loadBlockmeter(_rtdConfig) { + if (!(_rtdConfig.params && _rtdConfig.params.pub) || !isStr(_rtdConfig.params && _rtdConfig.params.pub) || isEmptyStr(_rtdConfig.params && _rtdConfig.params.pub)) { + logError(`${_config.MODULE}: params.pub should be a string`); + return false; + } + + const params = []; + params.push(`pub=${_rtdConfig.params.pub}`); + params.push(`dn=${window.location.hostname}`); + + let url = _rtdConfig.params.url; + if (!url || isEmptyStr(url)) { + logWarn(`${_config.MODULE}: params.url is missing, using default url.`); + url = `${_config.BLOCKMETER_URL}?ver=${_config.VERSION}`; + } + + const scriptUrl = `https://${url}&${params.join('&')}`; + loadExternalScript(scriptUrl, _config.MODULE); + return true; +} + +function markAdBlockInventory(codes, _rtdConfig, _userConsent) { + return codes.reduce((targets, code) => { + targets[code] = targets[code] || {}; + const getAaxTargets = () => isFn(window.aax.getTargetingData) + ? window.aax.getTargetingData(code, _rtdConfig, _userConsent) + : {}; + targets[code] = { + [_config.ADSERVER_TARGETING_KEY]: code, + ...getAaxTargets() + }; + return targets; + }, {}); +} + +export const aaxBlockmeterRtdModule = { + name: _config.MODULE, + init: loadBlockmeter, + getTargetingData: markAdBlockInventory, +}; + +function registerSubModule() { + submodule('realTimeData', aaxBlockmeterRtdModule); +} + +registerSubModule(); diff --git a/modules/aaxBlockmeterRtdProvider.md b/modules/aaxBlockmeterRtdProvider.md new file mode 100644 index 00000000000..0a317f85b85 --- /dev/null +++ b/modules/aaxBlockmeterRtdProvider.md @@ -0,0 +1,48 @@ +## Overview + +Module Name: AAX Blockmeter Realtime Data Module +Module Type: Rtd Provider +Maintainer: product@aax.media + +## Description + +The module enables publishers to measure traffic coming from visitors using adblockers. + +AAX can also help publishers monetize this traffic by allowing them to serve [acceptable ads](https://acceptableads.com/about/) to these adblock visitors and recover their lost revenue. [Reach out to us](https://www.aax.media/try-blockmeter/) to know more. + +## Integration + +Build the AAX Blockmeter Realtime Data Module into the Prebid.js package with: + +``` +gulp build --modules=aaxBlockmeterRtdProvider,rtdModule +``` + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders` object. + +| Name | Scope | Description | Example | Type | +|:----------:|:--------:|:-----------------------------|:---------------:|:------:| +| `name` | required | Real time data module name | `'aaxBlockmeter'` | `string` | +| `params` | required | | | `Object` | +| `params.pub` | required | AAX to share pub ID, [Reach out to us](https://www.aax.media/try-blockmeter/) to know more! | `'AAX00000'` | `string` | +| `params.url` | optional | AAX Blockmeter Script Url. Defaults to `'c.aaxads.com/aax.js?ver=1.2'` | `'c.aaxads.com/aax.js?ver=1.2'` | `string` | + +### Example + +```javascript +pbjs.setConfig({ + "realTimeData": { + "dataProviders": [ + { + "name": "aaxBlockmeter", + "params": { + "pub": "AAX00000", + "url": "c.aaxads.com/aax.js?ver=1.2", + } + } + ] + } +}) +``` diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index e59fd341bae..adb9744428f 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -15,6 +15,7 @@ import { isFn, isInteger, isNumber, + isArrayOfNums, logError, logInfo, logWarn, @@ -48,33 +49,32 @@ const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0k const ADAGIO_PUBKEY_E = 65537; const CURRENCY = 'USD'; -// This provide a whitelist and a basic validation -// of OpenRTB 2.5 options used by the Adagio SSP. -// https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf +// This provide a whitelist and a basic validation of OpenRTB 2.6 options used by the Adagio SSP. +// https://iabtechlab.com/wp-content/uploads/2022/04/OpenRTB-2-6_FINAL.pdf export const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), 'minduration': (value) => isInteger(value), 'maxduration': (value) => isInteger(value), - 'protocols': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].indexOf(v) !== -1), + 'protocols': (value) => isArrayOfNums(value), 'w': (value) => isInteger(value), 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), - 'placement': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5].indexOf(v) !== -1), - 'linearity': (value) => [1, 2].indexOf(value) !== -1, - 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'placement': (value) => isInteger(value), + 'linearity': (value) => isInteger(value), + 'skip': (value) => isInteger(value), 'skipmin': (value) => isInteger(value), 'skipafter': (value) => isInteger(value), 'sequence': (value) => isInteger(value), - 'battr': (value) => Array.isArray(value) && value.every(v => Array.from({length: 17}, (_, i) => i + 1).indexOf(v) !== -1), + 'battr': (value) => isArrayOfNums(value), 'maxextended': (value) => isInteger(value), 'minbitrate': (value) => isInteger(value), 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, - 'playbackmethod': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1), - 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, - 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1, - 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1, - 'api': (value) => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1) + 'boxingallowed': (value) => isInteger(value), + 'playbackmethod': (value) => isArrayOfNums(value), + 'playbackend': (value) => isInteger(value), + 'delivery': (value) => isInteger(value), + 'pos': (value) => isInteger(value), + 'api': (value) => isArrayOfNums(value) }; let currentWindow; @@ -669,10 +669,8 @@ function autoFillParams(bid) { } // extra params - setExtraParam(bid, 'environment'); setExtraParam(bid, 'pagetype'); setExtraParam(bid, 'category'); - setExtraParam(bid, 'subcategory'); } function getPageDimensions() { @@ -761,46 +759,51 @@ function getSlotPosition(adUnitElementId) { position.x = Math.round(sfGeom.t); position.y = Math.round(sfGeom.l); } else if (canAccessTopWindow()) { - // window.top based computing - const wt = getWindowTop(); - const d = wt.document; + try { + // window.top based computing + const wt = getWindowTop(); + const d = wt.document; - let domElement; + let domElement; - if (inIframe() === true) { - const ws = getWindowSelf(); - const currentElement = ws.document.getElementById(adUnitElementId); - domElement = internal.getElementFromTopWindow(currentElement, ws); - } else { - domElement = wt.document.getElementById(adUnitElementId); - } + if (inIframe() === true) { + const ws = getWindowSelf(); + const currentElement = ws.document.getElementById(adUnitElementId); + domElement = internal.getElementFromTopWindow(currentElement, ws); + } else { + domElement = wt.document.getElementById(adUnitElementId); + } - if (!domElement) { - return ''; - } + if (!domElement) { + return ''; + } - let box = domElement.getBoundingClientRect(); - - const docEl = d.documentElement; - const body = d.body; - const clientTop = d.clientTop || body.clientTop || 0; - const clientLeft = d.clientLeft || body.clientLeft || 0; - const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - const elComputedStyle = wt.getComputedStyle(domElement, null); - const elComputedDisplay = elComputedStyle.display || 'block'; - const mustDisplayElement = elComputedDisplay === 'none'; - - if (mustDisplayElement) { - domElement.style = domElement.style || {}; - const originalDisplay = domElement.style.display; - domElement.style.display = 'block'; - box = domElement.getBoundingClientRect(); - domElement.style.display = originalDisplay || null; + let box = domElement.getBoundingClientRect(); + + const docEl = d.documentElement; + const body = d.body; + const clientTop = d.clientTop || body.clientTop || 0; + const clientLeft = d.clientLeft || body.clientLeft || 0; + const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const elComputedStyle = wt.getComputedStyle(domElement, null); + const elComputedDisplay = elComputedStyle.display || 'block'; + const mustDisplayElement = elComputedDisplay === 'none'; + + if (mustDisplayElement) { + domElement.style = domElement.style || {}; + const originalDisplay = domElement.style.display; + domElement.style.display = 'block'; + box = domElement.getBoundingClientRect(); + domElement.style.display = originalDisplay || null; + } + position.x = Math.round(box.left + scrollLeft - clientLeft); + position.y = Math.round(box.top + scrollTop - clientTop); + } catch (err) { + logError(LOG_PREFIX, err); + return ''; } - position.x = Math.round(box.left + scrollLeft - clientLeft); - position.y = Math.round(box.top + scrollTop - clientTop); } else { return ''; } @@ -1089,8 +1092,6 @@ export const spec = { bidObj.placement = bidReq.params.placement; bidObj.pagetype = bidReq.params.pagetype; bidObj.category = bidReq.params.category; - bidObj.subcategory = bidReq.params.subcategory; - bidObj.environment = bidReq.params.environment; } bidResponses.push(bidObj); }); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index 9b48caa8233..f10c0a75e4d 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -17,12 +17,10 @@ Below, the list of Adagio params and where they can be set. | Param name | Global config | AdUnit config | | ---------- | ------------- | ------------- | | siteId | x | -| organizationId (obsolete) | | x -| site (obsolete) | | x +| organizationId * | | x +| site * | | x | pagetype | x | x -| environment | x | x | category | x | x -| subcategory | x | x | useAdUnitCodeAsAdUnitElementId | x | x | useAdUnitCodeAsPlacement | x | x | placement | | x @@ -31,6 +29,8 @@ Below, the list of Adagio params and where they can be set. | video | | x | native | | x +_* These params are deprecated in favor the Global configuration setup, see below._ + ### Global configuration The global configuration is used to store params once instead of duplicate them to each adUnit. The values will be used as "params" in the ad-request. @@ -49,9 +49,7 @@ pbjs.setConfig({ // - underscores `_` // Also, each param can have at most 50 unique active values (case-insensitive). pagetype: 'article', // Highly recommended. The pagetype describes what kind of content will be present in the page. - environment: 'mobile', // Recommended. Environment where the page is displayed. category: 'sport', // Recommended. Category of the content displayed in the page. - subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. useAdUnitCodeAsAdUnitElementId: false, // Optional. Use it by-pass adUnitElementId and use the adUnit code as value useAdUnitCodeAsPlacement: false, // Optional. Use it to by-pass placement and use the adUnit code as value }, @@ -62,9 +60,7 @@ pbjs.setConfig({ Adagio will use FPD data as fallback for the params below: - pagetype -- environment - category -- subcategory If the FPD value is an array, the 1st value of this array will be used. @@ -85,8 +81,10 @@ var adUnits = [ cpm: 3.00 // default to 1.00 }, video: { + api: [2, 7], // Required - Your video player must at least support the value 2 and/or 7. + playbackMethod: [6], // Highly recommended skip: 0 - // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. + // OpenRTB video options defined here override ones defined in mediaTypes. }, native: { // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. @@ -107,9 +105,7 @@ var adUnits = [ debug: true, adagio: { pagetype: 'article', - environment: 'mobile', category: 'sport', - subcategory: 'handball', useAdUnitCodeAsAdUnitElementId: false, useAdUnitCodeAsPlacement: false, } @@ -208,12 +204,6 @@ var adUnits = [ return bidResponse.site; } }, - { - key: "environment", - val: function (bidResponse) { - return bidResponse.environment; - } - }, { key: "placement", val: function (bidResponse) { @@ -231,12 +221,6 @@ var adUnits = [ val: function (bidResponse) { return bidResponse.category; } - }, - { - key: "subcategory", - val: function (bidResponse) { - return bidResponse.subcategory; - } } ] } diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index 977d161b214..33a85a81525 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -1,10 +1,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {includes} from '../src/polyfill.js'; -import {BANNER} from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { includes } from '../src/polyfill.js'; +import { BANNER } from '../src/mediaTypes.js'; -const VERSION = '1.0'; +const VERSION = '3.2'; const BAD_WORD_STEP = 0.1; const BAD_WORD_MIN = 0.2; +const ADHASH_BIDDER_CODE = 'adhash'; /** * Function that checks the page where the ads are being served for brand safety. @@ -54,43 +56,92 @@ function brandSafety(badWords, maxScore) { return positive ? result : -result; }; + /** + * Checks what rule will match in the given array with words + * @param {string} rule rule type (full, partial, starts, ends, regexp) + * @param {string} decodedWord decoded word + * @param {array} wordsToMatch array to find a match + * @returns {object|boolean} matched rule and occurances. If nothing is matched returns false + */ + const wordsMatchedWithRule = function (rule, decodedWord, wordsToMatch) { + if (rule === 'full' && wordsToMatch && wordsToMatch.includes(decodedWord)) { + return { rule, occurances: wordsToMatch.filter(element => element === decodedWord).length }; + } else if (rule === 'partial' && wordsToMatch && wordsToMatch.some(element => element.indexOf(decodedWord) > -1)) { + return { rule, occurances: wordsToMatch.filter(element => element.indexOf(decodedWord) > -1).length }; + } else if (rule === 'starts' && wordsToMatch && wordsToMatch.some(word => word.startsWith(decodedWord))) { + return { rule, occurances: wordsToMatch.filter(element => element.startsWith(decodedWord)).length }; + } else if (rule === 'ends' && wordsToMatch && wordsToMatch.some(word => word.endsWith(decodedWord))) { + return { rule, occurances: wordsToMatch.filter(element => element.endsWith(decodedWord)).length }; + } else if (rule === 'regexp' && wordsToMatch && wordsToMatch.some(element => element.match(new RegExp(decodedWord, 'i')))) { + return { rule, occurances: wordsToMatch.filter(element => element.match(new RegExp(decodedWord, 'i'))).length }; + } + return false; + }; + // Default parameters if the bidder is unable to send some of them badWords = badWords || []; maxScore = parseInt(maxScore) || 10; try { let score = 0; + const decodedUrl = decodeURI(window.top.location.href.substring(window.top.location.origin.length)); + const wordsAndNumbersInUrl = decodedUrl + .replaceAll(/[-,\._/\?=&#%]/g, ' ') + .replaceAll(/\s\s+/g, ' ') + .toLowerCase() + .trim(); const content = window.top.document.body.innerText.toLowerCase(); - const words = content.trim().split(/\s+/).length; + const contentWords = content.trim().split(/\s+/).length; + // \p{L} matches a single unicode code point in the category 'letter'. Matches any kind of letter from any language. + const regexp = new RegExp('[\\p{L}]+', 'gu'); + const words = content.match(regexp); + const wordsInUrl = wordsAndNumbersInUrl.match(regexp); + for (const [word, rule, points] of badWords) { - if (rule === 'full' && new RegExp('\\b' + rot13(word) + '\\b', 'i').test(content)) { - const occurances = content.match(new RegExp('\\b' + rot13(word) + '\\b', 'g')).length; - score += scoreCalculator(points, occurances); - } else if (rule === 'partial' && content.indexOf(rot13(word.toLowerCase())) > -1) { - const occurances = content.match(new RegExp(rot13(word), 'g')).length; - score += scoreCalculator(points, occurances); + const decodedWord = rot13(word.toLowerCase()); + + // Checks the words in the url of the page only for negative words. Don't serve any ad when at least one match is found + if (points > 0) { + const matchedRuleInUrl = wordsMatchedWithRule(rule, decodedWord, wordsInUrl); + if (matchedRuleInUrl.rule) { + return false; + } + } + + // Check if site content's words match any of our brand safety rules + const matchedRule = wordsMatchedWithRule(rule, decodedWord, words); + if (matchedRule.rule === 'full') { + score += scoreCalculator(points, matchedRule.occurances); + } else if (matchedRule.rule === 'partial') { + score += scoreCalculator(points, matchedRule.occurances); + } else if (matchedRule.rule === 'starts') { + score += scoreCalculator(points, matchedRule.occurances); + } else if (matchedRule.rule === 'ends') { + score += scoreCalculator(points, matchedRule.occurances); + } else if (matchedRule.rule === 'regexp') { + score += scoreCalculator(points, matchedRule.occurances); } } - return score < maxScore * words / 500; + return score < (maxScore * contentWords) / 1000; } catch (e) { return true; } } export const spec = { - code: 'adhash', - url: 'https://bidder.adhash.com/rtb?version=' + VERSION + '&prebid=true', + code: ADHASH_BIDDER_CODE, supportedMediaTypes: [ BANNER ], isBidRequestValid: (bid) => { try { - const { publisherId, platformURL } = bid.params; + const { publisherId, platformURL, bidderURL } = bid.params; return ( includes(Object.keys(bid.mediaTypes), BANNER) && typeof publisherId === 'string' && publisherId.length === 42 && typeof platformURL === 'string' && - platformURL.length >= 13 + platformURL.length >= 13 && + (!bidderURL || bidderURL.indexOf('https://') === 0) ); } catch (error) { return false; @@ -98,24 +149,51 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { + const storage = getStorageManager({ bidderCode: ADHASH_BIDDER_CODE }); const { gdprConsent } = bidderRequest; - const { url } = spec; const bidRequests = []; - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - // TODO: is 'page' the right value here? - referrer = bidderRequest.refererInfo.page; - } - for (var i = 0; i < validBidRequests.length; i++) { - var index = Math.floor(Math.random() * validBidRequests[i].sizes.length); - var size = validBidRequests[i].sizes[index].join('x'); + const body = document.body; + const html = document.documentElement; + const pageHeight = Math.max( + body.scrollHeight, + body.offsetHeight, + html.clientHeight, + html.scrollHeight, + html.offsetHeight + ); + const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); + + for (let i = 0; i < validBidRequests.length; i++) { + const bidderURL = validBidRequests[i].params.bidderURL || 'https://bidder.adhash.com'; + const url = `${bidderURL}/rtb?version=${VERSION}&prebid=true`; + const index = Math.floor(Math.random() * validBidRequests[i].sizes.length); + const size = validBidRequests[i].sizes[index].join('x'); + + let recentAds = []; + if (storage.localStorageIsEnabled()) { + const prefix = validBidRequests[i].params.prefix || 'adHash'; + recentAds = JSON.parse(storage.getDataFromLocalStorage(prefix + 'recentAds') || '[]'); + } + + // Needed for the ad density calculation + var adHeight = validBidRequests[i].sizes[index][1]; + var adWidth = validBidRequests[i].sizes[index][0]; + if (!window.adsCount) { + window.adsCount = 0; + } + if (!window.adsTotalSurface) { + window.adsTotalSurface = 0; + } + window.adsTotalSurface += adHeight * adWidth; + window.adsCount++; + bidRequests.push({ method: 'POST', url: url + '&publisher=' + validBidRequests[i].params.publisherId, bidRequest: validBidRequests[i], data: { timezone: new Date().getTimezoneOffset() / 60, - location: referrer, + location: bidderRequest.refererInfo ? bidderRequest.refererInfo.topmostLocation : '', publisherId: validBidRequests[i].params.publisherId, size: { screenWidth: window.screen.width, @@ -131,10 +209,14 @@ export const spec = { position: validBidRequests[i].adUnitCode }], blockedCreatives: [], - currentTimestamp: new Date().getTime(), - recentAds: [], + currentTimestamp: (new Date().getTime() / 1000) | 0, + recentAds: recentAds, GDPRApplies: gdprConsent ? gdprConsent.gdprApplies : null, - GDPR: gdprConsent ? gdprConsent.consentString : null + GDPR: gdprConsent ? gdprConsent.consentString : null, + servedAdsCount: window.adsCount, + adsTotalSurface: window.adsTotalSurface, + pageHeight: pageHeight, + pageWidth: pageWidth }, options: { withCredentials: false, @@ -157,7 +239,11 @@ export const spec = { } const publisherURL = JSON.stringify(request.bidRequest.params.platformURL); + const bidderURL = request.bidRequest.params.bidderURL || 'https://bidder.adhash.com'; const oneTimeId = request.bidRequest.adUnitCode + Math.random().toFixed(16).replace('0.', '.'); + const globalScript = !request.bidRequest.params.globalScript + ? `` + : ''; const bidderResponse = JSON.stringify({ responseText: JSON.stringify(responseBody) }); const requestData = JSON.stringify(request.data); @@ -165,8 +251,7 @@ export const spec = { requestId: request.bidRequest.bidId, cpm: responseBody.creatives[0].costEUR, ad: - `
- + `
${globalScript} `, width: request.bidRequest.sizes[0][0], height: request.bidRequest.sizes[0][1], diff --git a/modules/adhashBidAdapter.md b/modules/adhashBidAdapter.md index 4ee6ed3dc83..acca5a1e651 100644 --- a/modules/adhashBidAdapter.md +++ b/modules/adhashBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: AdHash Bidder Adapter Module Type: Bidder Adapter -Maintainer: damyan@adhash.org +Maintainer: damyan@adhash.com ``` # Description @@ -14,8 +14,6 @@ Here is what you need for Prebid integration with AdHash: 3. Use the Publisher ID and Platform URL as parameters in params. Please note that a number of AdHash functionalities are not supported in the Prebid.js integration: -* Cookie-less frequency and recency capping; -* Audience segments; * Price floors and passback tags, as they are not needed in the Prebid.js setup; * Reservation for direct deals only, as bids are evaluated based on their price. diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index ff7d3be6ebf..5736c7c19ba 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -79,7 +79,6 @@ export const spec = { {code: 'audiencemedia'}, {code: 'waardex_ak'}, {code: 'roqoon'}, - {code: 'andbeyond'}, {code: 'adbite'}, {code: 'houseofpubs'}, {code: 'torchad'}, @@ -97,7 +96,9 @@ export const spec = { {code: 'felixads'}, {code: 'motionspots'}, {code: 'sonic_twist'}, - {code: 'displayioads'} + {code: 'displayioads'}, + {code: 'rtbdemand_com'}, + {code: 'bidbuddy'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index 203b118652e..d77ee25ab5f 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -34,19 +34,21 @@ When tracking video you have two options: To view an [example of an Adloox integration](../integrationExamples/gpt/adloox.html): - gulp serve --nolint --notest --modules=gptPreAuction,categoryTranslation,dfpAdServerVideo,rtdModule,instreamTracking,rubiconBidAdapter,spotxBidAdapter,adlooxAnalyticsAdapter,adlooxAdServerVideo,adlooxRtdProvider + gulp serve --nolint --notest --modules=gptPreAuction,categoryTranslation,dfpAdServerVideo,intersectionRtdProvider,rtdModule,instreamTracking,rubiconBidAdapter,spotxBidAdapter,adlooxAnalyticsAdapter,adlooxAdServerVideo,adlooxRtdProvider **N.B.** `categoryTranslation` is required by `dfpAdServerVideo` that otherwise causes a JavaScript console warning +**N.B.** `intersectionRtdProvider` is used by `adlooxRtdProvider` to provide (above-the-fold) ATF measurement, if not enabled the `atf` segment will not be available + Now point your browser at: http://localhost:9999/integrationExamples/gpt/adloox.html?pbjs_debug=true ### Public Example -The example is published publically at: https://storage.googleapis.com/adloox-ads-js-test/prebid.html?pbjs_debug=true +The example is published publicly at: https://storage.googleapis.com/adloox-ads-js-test/prebid.html?pbjs_debug=true **N.B.** this will show a [CORS error](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors) for the request `https://p.adlooxtracking.com/q?...` that is safe to ignore on the public example page; it is related to the [RTD integration](./adlooxRtdProvider.md) which requires pre-registration of your sites -It is recommended you use [Google Chrome's 'Local Overrides' located in the Developer Tools panel](https://www.trysmudford.com/blog/chrome-local-overrides/) to explore the example without the inconvience of having to run your own web server. +It is recommended you use [Google Chrome's 'Local Overrides' located in the Developer Tools panel](https://www.trysmudford.com/blog/chrome-local-overrides/) to explore the example without the inconvenience of having to run your own web server. #### Pre-built `prebid.js` diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index 8862ac8ac47..c2037429185 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -6,11 +6,13 @@ * @module modules/adlooxRtdProvider * @requires module:modules/realTimeData * @requires module:modules/adlooxAnalyticsAdapter + * @optional module:modules/intersectionRtdProvider */ /* eslint standard/no-callback-literal: "off" */ /* eslint prebid/validate-imports: "off" */ +import {auctionManager} from '../src/auctionManager.js'; import {command as analyticsCommand, COMMAND} from './adlooxAnalyticsAdapter.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; @@ -18,142 +20,31 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {getRefererInfo} from '../src/refererDetection.js'; import { _each, + _map, + buildUrl, deepAccess, + deepClone, deepSetValue, - getAdUnitSizes, getGptSlotInfoForAdUnitCode, isArray, isBoolean, isInteger, isPlainObject, - isStr, logError, logInfo, logWarn, - mergeDeep + mergeDeep, + parseUrl, + safeJSONParse } from '../src/utils.js'; -import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'adloox'; const MODULE = `${MODULE_NAME}RtdProvider`; const API_ORIGIN = 'https://p.adlooxtracking.com'; const SEGMENT_HISTORIC = { 'a': 'aud', 'd': 'dis', 'v': 'vid' }; -const SEGMENT_HISTORIC_VALUES = Object.keys(SEGMENT_HISTORIC).map(k => SEGMENT_HISTORIC[k]); -const ADSERVER_TARGETING_PREFIX = 'adl_'; - -const CREATIVE_WIDTH_MIN = 20; -const CREATIVE_HEIGHT_MIN = 20; -const CREATIVE_AREA_MIN = CREATIVE_WIDTH_MIN * CREATIVE_HEIGHT_MIN; -// try to avoid using IntersectionObserver as it has unbounded (observed multi-second) latency -let intersectionObserver = window == top ? false : undefined; -const intersectionObserverElements = []; -// .map/.findIndex are safe here as they are only used for intersectionObserver -function atf(adUnit, cb) { - analyticsCommand(COMMAND.TOSELECTOR, { bid: { adUnitCode: adUnit.code } }, function(selector) { - const element = document.querySelector(selector); - if (!element) return cb(null); - - if (window.getComputedStyle(element)['display'] == 'none') return cb(NaN); - - try { - // short circuit for cross-origin - if (intersectionObserver) throw false; - - // Google's advice is to collapse slots on no fill but - // we have to cater to clients that grow slots on fill - const rect = (function(rect) { - const sizes = getAdUnitSizes(adUnit); - - if (sizes.length == 0) return false; - // interstitial (0x0, 1x1) - if (sizes.length == 1 && (sizes[0][0] * sizes[0][1]) <= 1) return true; - // try to catch premium slots (coord=0,0) as they will likely bounce into view - if (rect.top <= -window.pageYOffset && rect.left <= -window.pageXOffset && rect.top == rect.bottom) return true; - - // pick the smallest creative size as many publishers will just leave the element unbounded in the vertical - let width = Infinity; - let height = Infinity; - for (let i = 0; i < sizes.length; i++) { - const area = sizes[i][0] * sizes[i][1]; - if (area < CREATIVE_AREA_MIN) continue; - if (area < (width * height)) { - width = sizes[i][0]; - height = sizes[i][1]; - } - } - // we also scale the smallest size to the size of the slot as publishers resize units depending on viewport - const scale = Math.min(1, (rect.right - rect.left) / width); - - return { - left: rect.left, - right: rect.left + Math.max(CREATIVE_WIDTH_MIN, scale * width), - top: rect.top, - bottom: rect.top + Math.max(CREATIVE_HEIGHT_MIN, scale * height) - }; - })(element.getBoundingClientRect()); - - if (rect === false) return cb(NaN); - if (rect === true) return cb(1); - - const W = rect.right - rect.left; - const H = rect.bottom - rect.top; - - if (W * H < CREATIVE_AREA_MIN) return cb(NaN); - - let el; - let win = window; - while (1) { - // https://stackoverflow.com/a/8876069 - const vw = Math.max(win.document.documentElement.clientWidth || 0, win.innerWidth || 0); - const vh = Math.max(win.document.documentElement.clientHeight || 0, win.innerHeight || 0); - - // cut to viewport - rect.left = Math.min(Math.max(rect.left, 0), vw); - rect.right = Math.min(Math.max(rect.right, 0), vw); - rect.top = Math.min(Math.max(rect.top, 0), vh); - rect.bottom = Math.min(Math.max(rect.bottom, 0), vh); - - if (win == top) return cb(((rect.right - rect.left) * (rect.bottom - rect.top)) / (W * H)); - el = win.frameElement; - if (!el) throw false; // cross-origin - win = win.parent; - - // transpose to frame element - const frameElementRect = el.getBoundingClientRect(); - rect.left += frameElementRect.left; - rect.right = Math.min(rect.right + frameElementRect.left, frameElementRect.right); - rect.top += frameElementRect.top; - rect.bottom = Math.min(rect.bottom + frameElementRect.top, frameElementRect.bottom); - } - } catch (_) { - if (intersectionObserver === undefined) { - try { - intersectionObserver = new IntersectionObserver(function(entries) { - entries.forEach(entry => { - const ratio = entry.intersectionRect.width * entry.intersectionRect.height < CREATIVE_AREA_MIN ? NaN : entry.intersectionRatio; - const idx = intersectionObserverElements.findIndex(x => x.element == entry.target); - intersectionObserverElements[idx].cb.forEach(cb => cb(ratio)); - intersectionObserverElements.splice(idx, 1); - intersectionObserver.unobserve(entry.target); - }); - }); - } catch (_) { - intersectionObserver = false; - } - } - if (!intersectionObserver) return cb(null); - const idx = intersectionObserverElements.findIndex(x => x.element == element); - if (idx == -1) { - intersectionObserverElements.push({ element, cb: [ cb ] }); - intersectionObserver.observe(element); - } else { - intersectionObserverElements[idx].cb.push(cb); - } - } - }); -} +const ADSERVER_TARGETING_PREFIX = 'adl'; function init(config, userConsent) { logInfo(MODULE, 'init', config, userConsent); @@ -167,10 +58,6 @@ function init(config, userConsent) { logError(MODULE, 'invalid params'); return false; } - if (!(config.params.api_origin === undefined || isStr(config.params.api_origin))) { - logError(MODULE, 'invalid api_origin params value'); - return false; - } if (!(config.params.imps === undefined || (isInteger(config.params.imps) && config.params.imps > 0))) { logError(MODULE, 'invalid imps params value'); return false; @@ -213,193 +100,110 @@ function init(config, userConsent) { } function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ - const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits).map(adUnit => { - return { - gpid: deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot || adUnit.code, - unit: adUnit - }; - }).filter(adUnit => !!adUnit.gpid); - - let response = {}; - function setSegments() { - function val(v, k) { - if (!((SEGMENT_HISTORIC[k] || k == 'atf') && v >= 0)) return v; - return config.params.thresholds.filter(t => t <= v); - } - - const ortb2 = reqBidsConfigObj.ortb2Fragments?.global || {}; - const dataSite = deepAccess(ortb2, 'site.ext.data') || {}; - const dataUser = deepAccess(ortb2, 'user.ext.data') || {}; - - _each(response, (v0, k0) => { - if (k0 == '_') return; - const k = SEGMENT_HISTORIC[k0] || k0; - const v = val(v0, k0); - deepSetValue(k == k0 ? dataUser : dataSite, `${MODULE_NAME}_rtd.${k}`, v); - }); - deepSetValue(dataSite, `${MODULE_NAME}_rtd.ok`, true); + const adUnits0 = reqBidsConfigObj.adUnits || getGlobal().adUnits; + // adUnits must be ordered according to adUnitCodes for stable 's' param usage and handling the response below + const adUnits = reqBidsConfigObj.adUnitCodes.map(code => adUnits0.find(unit => unit.code == code)); + + // buildUrl creates PHP style multi-parameters and includes undefined... (╯°□°)╯ ┻━┻ + const url = buildUrl(mergeDeep(parseUrl(`${API_ORIGIN}/q`), { search: { + 'v': `pbjs-${getGlobal().version}`, + 'c': config.params.clientid, + 'p': config.params.platformid, + 't': config.params.tagid, + 'imp': config.params.imps, + 'fc_ip': config.params.freqcap_ip, + 'fc_ipua': config.params.freqcap_ipua, + 'pn': (getRefererInfo().page || '').substr(0, 300).split(/[?#]/)[0], + 's': _map(adUnits, function(unit) { + // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ + const gpid = deepAccess(unit, 'ortb2Imp.ext.gpid') || + deepAccess(unit, 'ortb2Imp.ext.data.pbadslot') || + getGptSlotInfoForAdUnitCode(unit.code).gptSlot || + unit.code; + const ref = [ gpid ]; + if (!config.params.slotinpath) ref.push(unit.code); + return ref.join('\t'); + }) + } })).replace(/\[\]|[^?&]+=undefined/g, '').replace(/([?&])&+/g, '$1'); + + ajax(url, + function(responseText, q) { + function val(v, k) { + if (!(SEGMENT_HISTORIC[k] && v >= 0)) return v; + return config.params.thresholds.filter(t => t <= v); + } - deepSetValue(ortb2, 'site.ext.data', dataSite); - deepSetValue(ortb2, 'user.ext.data', dataUser); - deepSetValue(reqBidsConfigObj, 'ortb2Fragments.global', ortb2); + const response = safeJSONParse(responseText); + if (!response) { + logError(MODULE, 'unexpected response'); + return callback(); + } - adUnits.forEach((adUnit, i) => { - _each(response['_'][i], (v0, k0) => { + const { site: ortb2site, user: ortb2user } = reqBidsConfigObj.ortb2Fragments.global; + _each(response, function(v0, k0) { + if (k0 == '_') return; const k = SEGMENT_HISTORIC[k0] || k0; const v = val(v0, k0); - deepSetValue(adUnit.unit, `ortb2Imp.ext.data.${MODULE_NAME}_rtd.${k}`, v); + deepSetValue(k == k0 ? ortb2user : ortb2site, `ext.data.${MODULE_NAME}_rtd.${k}`, v); }); - }); - }; - - // mergeDeep does not handle merging deep arrays... (╯°□°)╯ ┻━┻ - function mergeDeep(target, ...sources) { - function emptyValue(v) { - if (isPlainObject(v)) { - return {}; - } else if (isArray(v)) { - return []; - } else { - return undefined; - } - } - if (!sources.length) return target; - const source = sources.shift(); - - if (isPlainObject(target) && isPlainObject(source)) { - Object.keys(source).forEach(key => { - if (!(key in target)) target[key] = emptyValue(source[key]); - target[key] = target[key] !== undefined ? mergeDeep(target[key], source[key]) : source[key]; - }); - } else if (isArray(target) && isArray(source)) { - source.forEach((v, i) => { - if (!(i in target)) target[i] = emptyValue(v); - target[i] = target[i] !== undefined ? mergeDeep(target[i], v) : v; + _each(response._, function(segments, i) { + _each(segments, function(v0, k0) { + const k = SEGMENT_HISTORIC[k0] || k0; + const v = val(v0, k0); + deepSetValue(adUnits[i], `ortb2Imp.ext.data.${MODULE_NAME}_rtd.${k}`, v); + }); }); - } else { - target = source; - } - return mergeDeep(target, ...sources); - } + deepSetValue(ortb2site, `ext.data.${MODULE_NAME}_rtd.ok`, true); - let semaphore = 1; - function semaphoreInc(inc) { - if (semaphore == 0) return; - semaphore += inc; - if (semaphore == 0) { - setSegments() callback(); } - } - - const refererInfo = getRefererInfo(); - const args = [ - [ 'v', `pbjs-${getGlobal().version}` ], - [ 'c', config.params.clientid ], - [ 'p', config.params.platformid ], - [ 't', config.params.tagid ], - [ 'imp', config.params.imps ], - [ 'fc_ip', config.params.freqcap_ip ], - [ 'fc_ipua', config.params.freqcap_ipua ], - [ 'pn', (refererInfo.page || '').substr(0, 300).split(/[?#]/)[0] ] - ]; - - if (!adUnits.length) { - logWarn(MODULE, 'no suitable adUnits (missing pbadslot?)'); - } - const atfQueue = []; - adUnits.map((adUnit, i) => { - const ref = [ adUnit.gpid ]; - if (!config.params.slotinpath) ref.push(adUnit.unit.code); - args.push(['s', ref.join('\t')]); - - semaphoreInc(1); - atfQueue.push(function() { - atf(adUnit.unit, function(x) { - let viewable = document.visibilityState === undefined || document.visibilityState == 'visible'; - try { viewable = viewable && top.document.hasFocus() } catch (_) {} - logInfo(MODULE, `atf code=${adUnit.unit.code} has area=${x}, viewable=${viewable}`); - const atfList = []; atfList[i] = { atf: parseInt(x * 100) }; - response = mergeDeep(response, { _: atfList }); - semaphoreInc(-1); - }); - }); - }); - function atfCb() { - atfQueue.forEach(x => x()); - } - if (document.readyState == 'loading') { - document.addEventListener('DOMContentLoaded', atfCb, false); - } else { - atfCb(); - } - - analyticsCommand(COMMAND.URL, { - url: (config.params.api_origin || API_ORIGIN) + '/q?', - args: args - }, function(url) { - ajax(url, { - success: function(responseText, q) { - try { - if (q.getResponseHeader('content-type') == 'application/json') { - response = mergeDeep(response, JSON.parse(responseText)); - } else { - throw false; - } - } catch (_) { - logError(MODULE, 'unexpected response'); - } - semaphoreInc(-1); - }, - error: function(statusText, q) { - logError(MODULE, 'request failed'); - semaphoreInc(-1); - } - }); - }); + ); } function getTargetingData(adUnitArray, config, userConsent, auction) { - function targetingNormalise(v) { + function val(v) { if (isArray(v) && v.length == 0) return undefined; if (isBoolean(v)) v = ~~v; if (!v) return undefined; // empty string and zero return v; } - const ortb2 = auction.getFPD().global || {}; - const dataSite = deepAccess(ortb2, `site.ext.data.${MODULE_NAME}_rtd`) || {}; - if (!dataSite.ok) return {}; + const { site: ortb2site, user: ortb2user } = auctionManager.index.getAuction(auction).getFPD().global; - const dataUser = deepAccess(ortb2, `user.ext.data.${MODULE_NAME}_rtd`) || {}; - return getGlobal().adUnits.filter(adUnit => includes(adUnitArray, adUnit.code)).reduce((a, adUnit) => { - a[adUnit.code] = {}; + const ortb2base = {}; + _each(deepAccess(mergeDeep(ortb2site, ortb2user), `ext.data.${MODULE_NAME}_rtd`), function(v0, k) { + const v = val(v0); + if (v) ortb2base[`${ADSERVER_TARGETING_PREFIX}_${k}`] = v; + }); - _each(dataSite, (v0, k) => { - if (includes(SEGMENT_HISTORIC_VALUES, k)) return; // ignore site average viewability - const v = targetingNormalise(v0); - if (v) a[adUnit.code][ADSERVER_TARGETING_PREFIX + k] = v; - }); + const targeting = {}; + _each(auction.adUnits.filter(unit => adUnitArray.includes(unit.code)), function(unit) { + targeting[unit.code] = deepClone(ortb2base); - const adUnitSegments = deepAccess(adUnit, `ortb2Imp.ext.data.${MODULE_NAME}_rtd`, {}); - _each(Object.assign({}, dataUser, adUnitSegments), (v0, k) => { - const v = targetingNormalise(v0); - if (v) a[adUnit.code][ADSERVER_TARGETING_PREFIX + k] = v; + const ortb2imp = deepAccess(unit, `ortb2Imp.ext.data.${MODULE_NAME}_rtd`); + _each(ortb2imp, function(v0, k) { + const v = val(v0); + if (v) targeting[unit.code][`${ADSERVER_TARGETING_PREFIX}_${k}`] = v; }); - return a; - }, {}); + // ATF results shamelessly exfiltrated from intersectionRtdProvider + const bid = unit.bids.find(bid => !!bid.intersection); + if (bid) { + const v = val(config.params.thresholds.filter(t => t <= (bid.intersection.intersectionRatio * 100))); + if (v) targeting[unit.code][`${ADSERVER_TARGETING_PREFIX}_atf`] = v; + } + }); + + return targeting; } export const subModuleObj = { name: MODULE_NAME, init, getBidRequestData, - getTargetingData, - atf // used by adlooxRtdProvider_spec.js + getTargetingData }; submodule('realTimeData', subModuleObj); diff --git a/modules/adlooxRtdProvider.md b/modules/adlooxRtdProvider.md index 466f8ed1ba2..9d6a20a01a7 100644 --- a/modules/adlooxRtdProvider.md +++ b/modules/adlooxRtdProvider.md @@ -19,12 +19,7 @@ This provider fetches segments and populates the [First Party Data](https://docs * AdUnit segments are placed into `AdUnit.ortb2Imp.ext.data.adloox_rtd`: * **`{dis,vid,aud}`:** an list of integers describing the likelihood the AdUnit will be visible * **`atf`:** an list of integers describing the percentage of pixels visible at auction - * measured only once at pre-auction - * usable when the publisher uses the strategy of collapsing ad slots on no-fill - * using the reverse strategy, growing ad slots on fill, invalidates the measurement the position of all content (including the slots) changes post-auction - * works best when your page loads your ad slots have their actual size rendered (ie. not zero height) - * uses the smallest ad unit (above a threshold area of 20x20) supplied by the [publisher to Prebid.js](https://docs.prebid.org/dev-docs/examples/basic-example.html) and measures viewability as if that size to be used - * when used in cross-origin (unfriendly) IFRAME environments the ad slot is directly measured as is (ignoring publisher provided sizes) due to limitations in using [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) + * measured at pre-auction time using the [Intersection Module](https://docs.prebid.org/dev-docs/modules/intersectionRtdProvider.html); if not enabled then this measurement is not available **N.B.** this provider does not offer or utilise any user orientated data @@ -57,8 +52,13 @@ To use this, you *must* also integrate the [Adloox Analytics Adapter](./adlooxAn realTimeData: { auctionDelay: 100, // see below for guidance dataProviders: [ + { + name: 'intersection', + waitForIt: true + }, { name: 'adloox', + waitForIt: true, params: { // optional, defaults shown thresholds: [ 50, 60, 70, 80, 90 ], slotinpath: false diff --git a/modules/admaruBidAdapter.js b/modules/admaruBidAdapter.js index 65f62c77e26..f681a9a4191 100644 --- a/modules/admaruBidAdapter.js +++ b/modules/admaruBidAdapter.js @@ -5,6 +5,7 @@ const ADMARU_ENDPOINT = 'https://p1.admaru.net/AdCall'; const BIDDER_CODE = 'admaru'; const DEFAULT_BID_TTL = 360; +const SYNC_URL = 'https://p2.admaru.net/UserSync/sync' function parseBid(rawBid, currency) { const bid = {}; @@ -75,7 +76,24 @@ export const spec = { } return bidResponses; - } + }, + + getUserSyncs: function (syncOptions, responses) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: SYNC_URL + }]; + } + if (syncOptions.pixelEnabled) { + return [{ + type: 'image', + url: SYNC_URL + }]; + } + + return []; + }, } registerBidder(spec); diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js new file mode 100644 index 00000000000..808c788fcb9 --- /dev/null +++ b/modules/admaticBidAdapter.js @@ -0,0 +1,206 @@ +import { getValue, logError, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +let SYNC_URL = ''; +const BIDDER_CODE = 'admatic'; +export const spec = { + code: BIDDER_CODE, + aliases: [ + {code: 'pixad'} + ], + supportedMediaTypes: [BANNER, VIDEO], + /** f + * @param {object} bid + * @return {boolean} + */ + isBidRequestValid: (bid) => { + let isValid = false; + if (bid?.params) { + const isValidNetworkId = _validateId(getValue(bid.params, 'networkId')); + const isValidHost = _validateString(getValue(bid.params, 'host')); + isValid = isValidNetworkId && isValidHost; + } + + if (!isValid) { + logError(`${bid.bidder} networkId and host parameters are required. Bid aborted.`); + } + return isValid; + }, + + /** + * @param {BidRequest[]} validBidRequests + * @return {ServerRequest} + */ + buildRequests: (validBidRequests, bidderRequest) => { + const bids = validBidRequests.map(buildRequestObject); + const networkId = getValue(validBidRequests[0].params, 'networkId'); + const host = getValue(validBidRequests[0].params, 'host'); + const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; + const bidderName = validBidRequests[0].bidder; + + const payload = { + user: { + ua: navigator.userAgent + }, + blacklist: [], + site: { + page: location.href, + ref: location.origin, + publisher: { + name: location.hostname, + publisherId: networkId + } + }, + imp: bids, + ext: { + cur: currency, + bidder: bidderName + } + }; + + if (payload) { + switch (bidderName) { + case 'pixad': + SYNC_URL = 'https://static.pixad.com.tr/sync.html'; + break; + default: + SYNC_URL = 'https://cdn.serve.admatic.com.tr/showad/sync.html'; + break; + } + + return { method: 'POST', url: `https://${host}/pb?bidder=${bidderName}`, data: payload, options: { contentType: 'application/json' } }; + } + }, + + getUserSyncs: function (syncOptions, responses) { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: SYNC_URL + }]; + } + }, + + /** + * @param {*} response + * @param {ServerRequest} request + * @return {Bid[]} + */ + interpretResponse: (response, request) => { + const body = response.body || response; + const bidResponses = []; + if (body && body?.data && isArray(body.data)) { + body.data.forEach(bid => { + const resbid = { + requestId: bid.id, + cpm: bid.price, + width: bid.width, + height: bid.height, + currency: body.cur || 'TRY', + netRevenue: true, + ad: bid.party_tag, + creativeId: bid.creative_id, + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + }, + ttl: 360, + bidder: bid.bidder + }; + + bidResponses.push(resbid); + }); + } + return bidResponses; + } +}; + +function enrichSlotWithFloors(slot, bidRequest) { + try { + const slotFloors = {}; + + if (bidRequest.getFloor) { + if (bidRequest.mediaTypes?.banner) { + slotFloors.banner = {}; + const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) + bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = bidRequest.getFloor({ size: bannerSize, mediaType: BANNER })); + } + + if (bidRequest.mediaTypes?.video) { + slotFloors.video = {}; + const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) + videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = bidRequest.getFloor({ size: videoSize, mediaType: VIDEO })); + } + + if (Object.keys(slotFloors).length > 0) { + if (!slot) { + slot = {} + } + Object.assign(slot, { + floors: slotFloors + }); + } + } + } catch (e) { + logError('Could not parse floors from Prebid: ' + e); + } +} + +function parseSizes(sizes, parser = s => s) { + if (sizes == undefined) { + return []; + } + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parser(size)); + } + return [parser(sizes)]; // or a single one ? (ie. [728,90]) +} + +function parseSize(size) { + return size[0] + 'x' + size[1]; +} + +function buildRequestObject(bid) { + const reqObj = {}; + reqObj.size = getSizes(bid); + reqObj.id = getBidIdParameter('bidId', bid); + + enrichSlotWithFloors(reqObj, bid); + + return reqObj; +} + +function getSizes(bid) { + return concatSizes(bid); +} + +function concatSizes(bid) { + let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); + let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); + let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); + + if (isArray(bannerSizes) || isArray(playerSize) || isArray(videoSizes)) { + let mediaTypesSizes = [bannerSizes, videoSizes, playerSize]; + return mediaTypesSizes + .reduce(function(acc, currSize) { + if (isArray(currSize)) { + if (isArray(currSize[0])) { + currSize.forEach(function (childSize) { + acc.push({ w: childSize[0], h: childSize[1] }); + }) + } + } + return acc; + }, []); + } +} + +function _validateId(id) { + return (parseInt(id) > 0); +} + +function _validateString(str) { + return (typeof str == 'string'); +} + +registerBidder(spec); diff --git a/modules/admaticBidAdapter.md b/modules/admaticBidAdapter.md new file mode 100644 index 00000000000..2bf9afb3cdc --- /dev/null +++ b/modules/admaticBidAdapter.md @@ -0,0 +1,48 @@ +# Overview + +**Module Name**: Admatic Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@admatic.com.tr + +# Description + +Use `admatic` as bidder. + +`networkId` is required and must be integer. + +## AdUnits configuration example +``` + var adUnits = [{ + code: 'your-slot_1-div', //use exactly the same code as your slot div id. + sizes: [[300, 250]], + bids: [{ + bidder: 'admatic', + params: { + networkId: 12345, + host: 'layer.serve.admatic.com.tr' + } + }] + },{ + code: 'your-slot_2-div', //use exactly the same code as your slot div id. + sizes: [[600, 800]], + bids: [{ + bidder: 'admatic', + params: { + networkId: 12345, + host: 'layer.serve.admatic.com.tr' + } + }] + }]; +``` + +## UserSync example + +``` +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + syncEnabled: true, + syncDelay: 1 + } +}); +``` diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 8fcee60f9f9..8dbcfdafd32 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,12 +1,14 @@ -import { logError } from '../src/utils.js'; +import {logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; const BIDDER_CODE = 'admixer'; -const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads']; +const BIDDER_CODE_ADX = 'admixeradx'; +const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads', 'admixeradx']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; +const ADX_ENDPOINT_URL = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; export const spec = { code: BIDDER_CODE, aliases: ALIASES, @@ -57,6 +59,10 @@ export const spec = { if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + let bidFloor = getBidFloor(bidderRequest); + if (bidFloor) { + payload.bidFloor = bidFloor; + } } validRequest.forEach((bid) => { let imp = {}; @@ -65,7 +71,11 @@ export const spec = { }); return { method: 'POST', - url: endpointUrl || ENDPOINT_URL, + url: + endpointUrl || + (bidderRequest.bidderCode === BIDDER_CODE_ADX + ? ADX_ENDPOINT_URL + : ENDPOINT_URL), data: payload, }; }, @@ -96,4 +106,16 @@ export const spec = { return pixels; } }; +function getBidFloor(bid) { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0; + } +} registerBidder(spec); diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 0ae8411c073..ea3b723b316 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { isStr, deepAccess, logInfo } from '../src/utils.js'; +import { isStr, deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -8,6 +8,7 @@ const BIDDER_CODE = 'adnuntius'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' +// const DEFAULT_NATIVE = 'native' const checkSegment = function (segment) { if (isStr(segment)) return segment; @@ -27,6 +28,32 @@ const getSegmentsFromOrtb = function (ortb2) { return segments } +// function createNative(ad) { +// const native = {}; +// const assets = ad.assets +// native.title = ad.text.title.content; +// native.image = { +// url: assets.image.cdnId, +// height: assets.image.height, +// width: assets.image.width, +// }; +// if (assets.icon) { +// native.icon = { +// url: assets.icon.cdnId, +// height: assets.icon.height, +// width: assets.icon.width, +// }; +// } + +// native.sponsoredBy = ad.text.sponsoredBy?.content || ''; +// native.body = ad.text.body?.content || ''; +// native.cta = ad.text.cta?.content || ''; +// native.clickUrl = ad.destinationUrls.destination || ''; +// native.impressionTrackers = ad.impressionTrackingUrls || [ad.renderedPixel]; + +// return native; +// } + const handleMeta = function () { const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) let adnMeta = null @@ -82,6 +109,10 @@ export const spec = { network += '_video' } + // if (bid.mediaTypes && bid.mediaTypes.native) { + // network += '_native' + // } + bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); @@ -99,6 +130,7 @@ export const spec = { const network = networkKeys[j]; const networkRequest = [...request] if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } + // if (network.indexOf('_native') > -1) { networkRequest.push('tt=' + DEFAULT_NATIVE) } requests.push({ method: 'POST', url: ENDPOINT_URL + '?' + networkRequest.join('&'), @@ -137,13 +169,14 @@ export const spec = { if (adUnit.vastXml) { adResponse[adUnit.targetId].vastXml = adUnit.vastXml - adResponse[adUnit.targetId].mediaType = 'video' + adResponse[adUnit.targetId].mediaType = VIDEO + // } else if (ad.assets && ad.assets.image && ad.text && ad.text.title && ad.text.body && ad.destinationUrls && ad.destinationUrls.destination) { + // adResponse[adUnit.targetId].native = createNative(ad); + // adResponse[adUnit.targetId].mediaType = NATIVE; } else { adResponse[adUnit.targetId].ad = adUnit.html } - logInfo('BID', adResponse) - return adResponse } else return response }, {}); diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index 5d8b69af77f..c39d9e14f17 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getAdUnitSizes} from '../src/utils.js'; +import {getAdUnitSizes, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -23,7 +23,7 @@ export const internal = { if (bidderRequest && bidderRequest.gdprConsent) { return { consentString: bidderRequest.gdprConsent.consentString, - consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + consentRequired: (isBoolean(bidderRequest.gdprConsent.gdprApplies)) ? bidderRequest.gdprConsent.gdprApplies : true }; } @@ -60,8 +60,26 @@ export const internal = { */ extractBannerConfig: (bidRequest) => { const sizes = getAdUnitSizes(bidRequest); - if (Array.isArray(sizes) && sizes.length > 0) { - return { sizes: sizes }; + if (isArray(sizes) && !isEmpty(sizes)) { + const banner = { sizes: sizes }; + + // try to add floor for each banner size + banner.sizes.forEach(size => { + const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size }); + if (floor) { + size.push(floor.floor); + size.push(floor.currency); + } + }); + + // try to add default floor for banner + const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: '*' }); + if (floor) { + banner.floorPrice = floor.floor; + banner.floorCurrency = floor.currency; + } + + return banner; } return null; @@ -74,8 +92,17 @@ export const internal = { * @returns {null|Object.} */ extractNativeConfig: (bidRequest) => { - if (bidRequest && deepAccess(bidRequest, 'mediaTypes.native')) { - return bidRequest.mediaTypes.native; + if (bidRequest?.mediaTypes?.native) { + const native = bidRequest.mediaTypes.native; + + // try to add default floor for native + const floor = internal.getFloor(bidRequest, { mediaType: NATIVE, size: '*' }); + if (floor) { + native.floorPrice = floor.floor; + native.floorCurrency = floor.currency; + } + + return native; } return null; @@ -96,27 +123,25 @@ export const internal = { }, /** - * Extracts the floor price params from given bidRequest + * Try to get floor information via bidRequest.getFloor() * * @param {BidRequest} bidRequest - * @returns {undefined|float} + * @param {Object} options + * @returns {null|Object.} */ - extractFloorPrice: (bidRequest) => { - let floorPrice; - if (bidRequest && bidRequest.params && bidRequest.params.floor) { - // if there is a manual floorPrice set - floorPrice = !isNaN(parseInt(bidRequest.params.floor)) ? bidRequest.params.floor : undefined; - } - if (typeof bidRequest.getFloor === 'function') { - // use prebid floor module - let floorInfo; - try { - floorInfo = bidRequest.getFloor(); - } catch (e) {} - floorPrice = typeof floorInfo === 'object' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : floorPrice; + getFloor: (bidRequest, options) => { + if (!isFn(bidRequest?.getFloor)) { + return null; } - return floorPrice; + try { + const floor = bidRequest.getFloor(options); + if (isPlainObject(floor) && !isNaN(floor.floor)) { + return floor; + } + } catch {} + + return null; }, /** @@ -128,11 +153,11 @@ export const internal = { groupBidRequestsByPublisher: (bidRequests) => { const groupedBidRequests = {}; - if (!bidRequests || bidRequests.length === 0) { + if (!bidRequests || isEmpty(bidRequests)) { return groupedBidRequests; } - bidRequests.forEach((bidRequest) => { + bidRequests.forEach(bidRequest => { const publisher = internal.extractParams(bidRequest).publisher; if (!publisher) { return; @@ -205,7 +230,7 @@ export const spec = { const requests = []; // stop here on invalid or empty data - if (!bidderRequest || !validBidRequests || validBidRequests.length === 0) { + if (!bidderRequest || !validBidRequests || isEmpty(validBidRequests)) { return requests; } @@ -237,7 +262,7 @@ export const spec = { } // handle multiple bids per request - groupedBidRequests[publisher].forEach((bidRequest) => { + groupedBidRequests[publisher].forEach(bidRequest => { const bid = { bidId: bidRequest.bidId, transactionId: bidRequest.transactionId, @@ -257,10 +282,11 @@ export const spec = { bid.native = nativeConfig; } - // add floor price - const floorPrice = internal.extractFloorPrice(bidRequest); - if (floorPrice) { - bid.floorPrice = floorPrice; + // try to add default floor + const floor = internal.getFloor(bidRequest, { mediaType: '*', size: '*' }); + if (floor) { + bid.floorPrice = floor.floor; + bid.floorCurrency = floor.currency; } request.data.imp.push(bid); @@ -282,12 +308,12 @@ export const spec = { const bidResponses = []; // stop here on invalid or empty data - if (!response || !deepAccess(response, 'body.bids') || response.body.bids.length === 0) { + if (!response?.body?.bids || isEmpty(response.body.bids)) { return bidResponses; } // parse multiple bids per response - response.body.bids.forEach((bid) => { + response.body.bids.forEach(bid => { if (!bid || !bid.bid || !bid.creative) { return; } diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index b0b132abd1c..e6cdc7698bf 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -126,6 +126,8 @@ export const spec = { payload.userId = createEidsArray(bidderRequest.userId); } + payload.pbjs_version = '$prebid.version$'; + const data = JSON.stringify(payload); const options = { withCredentials: true @@ -227,17 +229,23 @@ function createEndpointQS(bidderRequest) { const qs = {}; if (bidderRequest) { const ref = bidderRequest.refererInfo; - if (ref?.location) { - // RefererUrl will be removed in a future version. - qs.RefererUrl = encodeURIComponent(ref.location); - if (ref.numIframes > 0) { - qs.SafeFrame = true; + if (ref) { + if (ref.location) { + // RefererUrl will be removed in a future version. + qs.RefererUrl = encodeURIComponent(ref.location); + if (!ref.reachedTop) { + qs.SafeFrame = true; + } } + + qs.PageUrl = encodeURIComponent(ref.topmostLocation); + qs.PageReferrer = encodeURIComponent(ref.location); } + // retreive info from ortb2 object if present (prebid7) const siteInfo = bidderRequest.ortb2?.site; if (siteInfo) { - qs.PageUrl = encodeURIComponent(siteInfo.page); + qs.PageUrl = encodeURIComponent(siteInfo.page || ref?.topmostLocation); qs.PageReferrer = encodeURIComponent(siteInfo.ref || ref?.location); } } diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js new file mode 100644 index 00000000000..e4d5c618b77 --- /dev/null +++ b/modules/aidemBidAdapter.js @@ -0,0 +1,521 @@ +import {_each, contains, deepAccess, deepSetValue, getDNT, isBoolean, isStr, isNumber, logError, logInfo} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'aidem'; +const BASE_URL = 'https://zero.aidemsrv.com'; +const LOCAL_BASE_URL = 'http://127.0.0.1:8787'; + +const AVAILABLE_CURRENCIES = ['USD']; +const DEFAULT_CURRENCY = ['USD']; // NOTE - USD is the only supported currency right now; Hardcoded for bids +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +const REQUIRED_VIDEO_PARAMS = [ 'mimes', 'protocols', 'context' ]; + +export const ERROR_CODES = { + BID_SIZE_INVALID_FORMAT: 1, + BID_SIZE_NOT_INCLUDED: 2, + PROPERTY_NOT_INCLUDED: 3, + SITE_ID_INVALID_VALUE: 4, + MEDIA_TYPE_NOT_SUPPORTED: 5, + PUBLISHER_ID_INVALID_VALUE: 6, + INVALID_RATELIMIT: 7, + PLACEMENT_ID_INVALID_VALUE: 8, +}; + +const endpoints = { + request: `${BASE_URL}/bid/request`, + notice: { + win: `${BASE_URL}/notice/win`, + timeout: `${BASE_URL}/notice/timeout`, + error: `${BASE_URL}/notice/error`, + } +}; + +export function setEndPoints(env = null, path = '', mediaType = BANNER) { + switch (env) { + case 'local': + endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout`; + break; + case 'main': + endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest`; + endpoints.notice.win = `${BASE_URL}${path}/notice/win`; + endpoints.notice.error = `${BASE_URL}${path}/notice/error`; + endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout`; + break; + } + return endpoints; +} + +config.getConfig('aidem', function (config) { + if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType); } +}); + +// AIDEM Custom FN +function recur(obj) { + var result = {}; var _tmp; + for (var i in obj) { + // enabledPlugin is too nested, also skip functions + if (!(i === 'enabledPlugin' || typeof obj[i] === 'function')) { + if (typeof obj[i] === 'object' && obj[i] !== null) { + // get props recursively + _tmp = recur(obj[i]); + // if object is not {} + if (Object.keys(_tmp).length) { + result[i] = _tmp; + } + } else { + // string, number or boolean + result[i] = obj[i]; + } + } + } + return result; +} + +// ================================================================================= +function getConnectionType() { + const connection = navigator.connection || navigator.webkitConnection; + if (!connection) { + return 0; + } + switch (connection.type) { + case 'ethernet': + return 1; + case 'wifi': + return 2; + case 'cellular': + switch (connection.effectiveType) { + case 'slow-2g': + return 4; + case '2g': + return 4; + case '3g': + return 5; + case '4g': + return 6; + case '5g': + return 7; + default: + return 3; + } + default: + return 0; + } +} + +function getDevice() { + const language = navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage; + return { + ua: navigator.userAgent, + dnt: !!getDNT(), + language: language, + connectiontype: getConnectionType(), + screen_width: screen.width, + screen_height: screen.height + }; +} + +function getRegs() { + let regs = {}; + const consentManagement = config.getConfig('consentManagement'); + const coppa = config.getConfig('coppa'); + if (consentManagement && !!(consentManagement.gdpr)) { + deepSetValue(regs, 'gdpr_applies', !!consentManagement.gdpr); + } else { + deepSetValue(regs, 'gdpr_applies', false); + } + if (consentManagement && deepAccess(consentManagement, 'usp.cmpApi') === 'static') { + deepSetValue(regs, 'usp_applies', !!deepAccess(consentManagement, 'usp')); + deepSetValue(regs, 'us_privacy', deepAccess(consentManagement, 'usp.consentData.getUSPData.uspString')); + } else { + deepSetValue(regs, 'usp_applies', false); + } + + if (isBoolean(coppa)) { + deepSetValue(regs, 'coppa_applies', !!coppa); + } else { + deepSetValue(regs, 'coppa_applies', false); + } + + return regs; +} + +function getPageUrl(bidderRequest) { + return bidderRequest?.refererInfo?.page; +} + +function buildWinNotice(bid) { + const params = bid.params[0]; + return { + publisherId: params.publisherId, + siteId: params.siteId, + placementId: params.placementId, + burl: deepAccess(bid, 'meta.burl'), + cpm: bid.cpm, + currency: bid.currency, + impid: deepAccess(bid, 'meta.impid'), + dsp_id: deepAccess(bid, 'meta.dsp_id'), + adUnitCode: bid.adUnitCode, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + ttl: bid.ttl, + requestTimestamp: bid.requestTimestamp, + responseTimestamp: bid.responseTimestamp, + }; +} + +function buildErrorNotice(prebidErrorResponse) { + return { + message: `Prebid.js: Server call for ${prebidErrorResponse.bidderCode} failed.`, + url: encodeURIComponent(getPageUrl(prebidErrorResponse)), + auctionId: prebidErrorResponse.auctionId, + bidderRequestId: prebidErrorResponse.bidderRequestId, + metrics: {} + }; +} + +function hasValidFloor(obj) { + if (!obj) return false; + const hasValue = !isNaN(Number(obj.value)); + const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency); + return hasValue && hasCurrency; +} + +function getMediaType(bidRequest) { + if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO; } + return BANNER; +} + +function getPrebidRequestFields(bidderRequest, bidRequests) { + const payload = {}; + // Base Payload Data + deepSetValue(payload, 'id', bidderRequest.auctionId); + // Impressions + setPrebidImpressionObject(bidRequests, payload); + // Device + deepSetValue(payload, 'device', getDevice()); + // Timeout + deepSetValue(payload, 'tmax', bidderRequest.timeout); + // Currency + deepSetValue(payload, 'cur', DEFAULT_CURRENCY); + // Timezone + deepSetValue(payload, 'tz', new Date().getTimezoneOffset()); + // Privacy Regs + deepSetValue(payload, 'regs', getRegs()); + // Site + setPrebidSiteObject(bidderRequest, payload); + // Environment + setPrebidRequestEnvironment(payload); + // AT auction type + deepSetValue(payload, 'at', 1); + + return payload; +} + +function setPrebidImpressionObject(bidRequests, payload) { + payload.imp = []; + _each(bidRequests, function (bidRequest) { + const impressionObject = {}; + // Placement or ad tag used to initiate the auction + deepSetValue(impressionObject, 'id', bidRequest.bidId); + // Transaction id + deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'transactionId')); + // placement id + deepSetValue(impressionObject, 'tagid', deepAccess(bidRequest, 'params.placementId', null)); + // Publisher id + deepSetValue(payload, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); + // Site id + deepSetValue(payload, 'site.id', deepAccess(bidRequest, 'params.siteId')); + const mediaType = getMediaType(bidRequest); + switch (mediaType) { + case 'banner': + setPrebidImpressionObjectBanner(bidRequest, impressionObject); + break; + case 'video': + setPrebidImpressionObjectVideo(bidRequest, impressionObject); + break; + } + + // Floor (optional) + setPrebidImpressionObjectFloor(bidRequest, impressionObject); + + impressionObject.imp_ext = {}; + + payload.imp.push(impressionObject); + }); +} + +function setPrebidSiteObject(bidderRequest, payload) { + deepSetValue(payload, 'site.domain', deepAccess(bidderRequest, 'refererInfo.domain')); + deepSetValue(payload, 'site.page', deepAccess(bidderRequest, 'refererInfo.page')); + deepSetValue(payload, 'site.referer', deepAccess(bidderRequest, 'refererInfo.ref')); + deepSetValue(payload, 'site.cat', deepAccess(bidderRequest, 'ortb2.site.cat')); + deepSetValue(payload, 'site.sectioncat', deepAccess(bidderRequest, 'ortb2.site.sectioncat')); + deepSetValue(payload, 'site.keywords', deepAccess(bidderRequest, 'ortb2.site.keywords')); + deepSetValue(payload, 'site.site_ext', deepAccess(bidderRequest, 'ortb2.site.ext')); // see https://docs.prebid.org/features/firstPartyData.html +} + +function setPrebidRequestEnvironment(payload) { + const __navigator = JSON.parse(JSON.stringify(recur(navigator))); + delete __navigator.plugins; + deepSetValue(payload, 'environment.ri', getRefererInfo()); + deepSetValue(payload, 'environment.hl', window.history.length); + deepSetValue(payload, 'environment.nav', __navigator); + deepSetValue(payload, 'environment.inp.euc', window.encodeURIComponent.name === 'encodeURIComponent' && typeof window.encodeURIComponent.prototype === 'undefined'); + deepSetValue(payload, 'environment.inp.eu', window.encodeURI.name === 'encodeURI' && typeof window.encodeURI.prototype === 'undefined'); + deepSetValue(payload, 'environment.inp.js', window.JSON.stringify.name === 'stringify' && typeof window.JSON.stringify.prototype === 'undefined'); + deepSetValue(payload, 'environment.inp.jp', window.JSON.parse.name === 'parse' && typeof window.JSON.parse.prototype === 'undefined'); + deepSetValue(payload, 'environment.inp.ofe', window.Object.fromEntries.name === 'fromEntries' && typeof window.Object.fromEntries.prototype === 'undefined'); + deepSetValue(payload, 'environment.inp.oa', window.Object.assign.name === 'assign' && typeof window.Object.assign.prototype === 'undefined'); + deepSetValue(payload, 'environment.wpar.innerWidth', window.innerWidth); + deepSetValue(payload, 'environment.wpar.innerHeight', window.innerHeight); +} + +function setPrebidImpressionObjectFloor(bidRequest, impressionObject) { + const floor = deepAccess(bidRequest, 'params.floor'); + if (hasValidFloor(floor)) { + deepSetValue(impressionObject, 'floor.value', floor.value); + deepSetValue(impressionObject, 'floor.currency', floor.currency); + } +} + +function setPrebidImpressionObjectBanner(bidRequest, impressionObject) { + deepSetValue(impressionObject, 'mediatype', BANNER); + deepSetValue(impressionObject, 'banner.topframe', 1); + deepSetValue(impressionObject, 'banner.format', []); + _each(bidRequest.mediaTypes.banner.sizes, function (bannerFormat) { + const format = {}; + deepSetValue(format, 'w', bannerFormat[0]); + deepSetValue(format, 'h', bannerFormat[1]); + deepSetValue(format, 'format_ext', {}); + impressionObject.banner.format.push(format); + }); +} + +function setPrebidImpressionObjectVideo(bidRequest, impressionObject) { + deepSetValue(impressionObject, 'mediatype', VIDEO); + deepSetValue(impressionObject, 'video.format', []); + deepSetValue(impressionObject, 'video.mimes', bidRequest.mediaTypes.video.mimes); + deepSetValue(impressionObject, 'video.minDuration', bidRequest.mediaTypes.video.minduration); + deepSetValue(impressionObject, 'video.maxDuration', bidRequest.mediaTypes.video.maxduration); + deepSetValue(impressionObject, 'video.protocols', bidRequest.mediaTypes.video.protocols); + deepSetValue(impressionObject, 'video.context', bidRequest.mediaTypes.video.context); + deepSetValue(impressionObject, 'video.playbackmethod', bidRequest.mediaTypes.video.playbackmethod); + deepSetValue(impressionObject, 'skip', bidRequest.mediaTypes.video.skip); + deepSetValue(impressionObject, 'skipafter', bidRequest.mediaTypes.video.skipafter); + deepSetValue(impressionObject, 'video.pos', bidRequest.mediaTypes.video.pos); + _each(bidRequest.mediaTypes.video.playerSize, function (videoPlayerSize) { + const format = {}; + deepSetValue(format, 'w', videoPlayerSize[0]); + deepSetValue(format, 'h', videoPlayerSize[1]); + deepSetValue(format, 'format_ext', {}); + impressionObject.video.format.push(format); + }); +} + +function getPrebidResponseBidObject(openRTBResponseBidObject) { + const prebidResponseBidObject = {}; + // Common properties + deepSetValue(prebidResponseBidObject, 'requestId', openRTBResponseBidObject.impid); + deepSetValue(prebidResponseBidObject, 'cpm', parseFloat(openRTBResponseBidObject.price)); + deepSetValue(prebidResponseBidObject, 'creativeId', openRTBResponseBidObject.crid); + deepSetValue(prebidResponseBidObject, 'currency', openRTBResponseBidObject.cur ? openRTBResponseBidObject.cur.toUpperCase() : DEFAULT_CURRENCY); + deepSetValue(prebidResponseBidObject, 'width', openRTBResponseBidObject.w); + deepSetValue(prebidResponseBidObject, 'height', openRTBResponseBidObject.h); + deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid); + deepSetValue(prebidResponseBidObject, 'netRevenue', true); + deepSetValue(prebidResponseBidObject, 'ttl', 60000); + + if (openRTBResponseBidObject.mediatype === VIDEO) { + logInfo('bidObject.mediatype == VIDEO'); + deepSetValue(prebidResponseBidObject, 'mediaType', VIDEO); + deepSetValue(prebidResponseBidObject, 'vastUrl', openRTBResponseBidObject.adm); + } else { + logInfo('bidObject.mediatype == BANNER'); + deepSetValue(prebidResponseBidObject, 'mediaType', BANNER); + deepSetValue(prebidResponseBidObject, 'ad', openRTBResponseBidObject.adm); + } + setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject); + return prebidResponseBidObject; +} + +function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { + logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); + deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); + if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { + const primaryCatId = openRTBResponseBidObject.cat.shift(); + deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); + deepSetValue(prebidResponseBidObject, 'meta.secondaryCatIds', openRTBResponseBidObject.cat); + } + deepSetValue(prebidResponseBidObject, 'meta.id', openRTBResponseBidObject.id); + deepSetValue(prebidResponseBidObject, 'meta.dsp_id', openRTBResponseBidObject.dsp_id); + deepSetValue(prebidResponseBidObject, 'meta.adid', openRTBResponseBidObject.adid); + deepSetValue(prebidResponseBidObject, 'meta.burl', openRTBResponseBidObject.burl); + deepSetValue(prebidResponseBidObject, 'meta.impid', openRTBResponseBidObject.impid); + deepSetValue(prebidResponseBidObject, 'meta.cat', openRTBResponseBidObject.cat); + deepSetValue(prebidResponseBidObject, 'meta.cid', openRTBResponseBidObject.cid); +} + +function hasValidMediaType(bidRequest) { + const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest); + if (!supported) { + logError('AIDEM Bid Adapter: media type not supported', { bidder: BIDDER_CODE, code: ERROR_CODES.MEDIA_TYPE_NOT_SUPPORTED }); + } + return supported; +} + +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +function hasVideoMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +function hasValidBannerMediaType(bidRequest) { + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + if (!sizes) { + logError('AIDEM Bid Adapter: media type sizes missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); + return false; + } + return true; +} + +function hasValidVideoMediaType(bidRequest) { + const sizes = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + if (!sizes) { + logError('AIDEM Bid Adapter: media type playerSize missing', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); + return false; + } + return true; +} + +function hasValidVideoParameters(bidRequest) { + let valid = true; + const adUnitsParameters = deepAccess(bidRequest, 'mediaTypes.video'); + const bidderParameter = deepAccess(bidRequest, 'params.video'); + for (let property of REQUIRED_VIDEO_PARAMS) { + const hasAdUnitParameter = adUnitsParameters.hasOwnProperty(property); + const hasBidderParameter = bidderParameter && bidderParameter.hasOwnProperty(property); + if (!hasAdUnitParameter && !hasBidderParameter) { + logError(`AIDEM Bid Adapter: ${property} is not included in either the adunit or params level`, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); + valid = false; + } + } + + return valid; +} + +function passesRateLimit(bidRequest) { + const rateLimit = deepAccess(bidRequest, 'params.rateLimit', 1); + if (!isNumber(rateLimit) || rateLimit > 1 || rateLimit < 0) { + logError('AIDEM Bid Adapter: invalid rateLimit (must be a number between 0 and 1)', { bidder: BIDDER_CODE, code: ERROR_CODES.INVALID_RATELIMIT }); + return false; + } + if (rateLimit !== 1) { + const randomRateValue = Math.random(); + if (randomRateValue > rateLimit) { + return false; + } + } + return true; +} + +function hasValidParameters(bidRequest) { + // Assigned from AIDEM to a publisher website + const siteId = deepAccess(bidRequest, 'params.siteId'); + const publisherId = deepAccess(bidRequest, 'params.publisherId'); + + if (!isStr(siteId)) { + logError('AIDEM Bid Adapter: siteId must valid string', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + return false; + } + + if (!isStr(publisherId)) { + logError('AIDEM Bid Adapter: publisherId must valid string', { bidder: BIDDER_CODE, code: ERROR_CODES.PUBLISHER_ID_INVALID_VALUE }); + return false; + } + + return true; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid: function(bidRequest) { + logInfo('bid: ', bidRequest); + + // check if request has valid mediaTypes + if (!hasValidMediaType(bidRequest)) return false; + + // check if request has valid media type parameters at adUnit level + if (hasBannerMediaType(bidRequest) && !hasValidBannerMediaType(bidRequest)) { + return false; + } + + if (hasVideoMediaType(bidRequest) && !hasValidVideoMediaType(bidRequest)) { + return false; + } + + if (hasVideoMediaType(bidRequest) && !hasValidVideoParameters(bidRequest)) { + return false; + } + + if (!hasValidParameters(bidRequest)) { + return false; + } + + return passesRateLimit(bidRequest); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + logInfo('validBidRequests: ', validBidRequests); + logInfo('bidderRequest: ', bidderRequest); + const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests); + const payloadString = JSON.stringify(prebidRequest); + + return { + method: 'POST', + url: endpoints.request, + data: payloadString, + options: { + withCredentials: true + } + }; + }, + + interpretResponse: function (serverResponse) { + const bids = []; + logInfo('serverResponse: ', serverResponse); + _each(serverResponse.body.bid, function (bidObject) { + logInfo('bidObject: ', bidObject); + if (!bidObject.price || !bidObject.adm) { + return; + } + logInfo('CPM OK'); + const bid = getPrebidResponseBidObject(bidObject); + bids.push(bid); + }); + return bids; + }, + + onBidWon: function(bid) { + // Bidder specific code + logInfo('onBidWon bid: ', bid); + const notice = buildWinNotice(bid); + ajax(endpoints.notice.win, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); + }, + + onBidderError: function({ bidderRequest }) { + // Bidder specific code + const notice = buildErrorNotice(bidderRequest); + ajax(endpoints.notice.error, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); + }, +}; +registerBidder(spec); diff --git a/modules/aidemBidAdapter.md b/modules/aidemBidAdapter.md new file mode 100644 index 00000000000..b59014c76ed --- /dev/null +++ b/modules/aidemBidAdapter.md @@ -0,0 +1,191 @@ +# Overview + +``` +name: AIDEM Adapter +type: Bidder Adapter +support: prebid@aidem.com +biddercode: aidem +``` + +# Description +This module connects publishers to AIDEM demand. + +This module is GDPR and CCPA compliant, and no 3rd party userIds are allowed. + + +## Global Bid Params +| Name | Scope | Description | Example | Type | +|---------------|----------|-------------------------|------------|----------| +| `siteId` | required | Unique site ID | `'ABCDEF'` | `String` | +| `publisherId` | required | Unique publisher ID | `'ABCDEF'` | `String` | +| `placementId` | optional | Unique publisher tag ID | `'ABCDEF'` | `String` | +| `rateLimit` | optional | Limit the volume sent to AIDEM. Must be between 0 and 1 | `0.6` | `Number` | + + +### Banner Bid Params +| Name | Scope | Description | Example | Type | +|------------|----------|--------------------------|---------------------------|---------| +| `sizes` | required | List of the sizes wanted | `[[300, 250], [300,600]]` | `Array` | + + +### Video Bid Params +| Name | Scope | Description | Example | Type | +|---------------|----------|-----------------------------------------|-----------------|-----------| +| `context` | required | One of instream, outstream, adpod | `'instream'` | `String` | +| `playerSize` | required | Width and height of the player | `'[640, 480]'` | `Array` | +| `maxduration` | required | Maximum video ad duration, in seconds | `30` | `Integer` | +| `minduration` | required | Minimum video ad duration, in seconds | `5` | `Integer` | +| `mimes` | required | List of the content MIME types supported by the player | `["video/mp4"]` | `Array` | +| `protocols` | required | An array of supported video protocols. At least one supported protocol must be specified, where: `2` = VAST 2.0 `3` = VAST 3.0 `5` = VAST 2.0 wrapper `6` = VAST 3.0 wrapper | `2` | `Array` | + + +### Additional Config +| Name | Scope | Description | Example | Type | +|---------------------|----------|---------------------------------------------------------|---------|-----------| +| `coppa` | optional | Child Online Privacy Protection Act | `true` | `Boolean` | +| `consentManagement` | optional | [Consent Management Object](#consent-management-object) | `{}` | `Object` | + + +### Consent Management Object +| Name | Scope | Description | Example | Type | +|--------|----------|--------------------------------------------------------------------------------------------------|---------|----------| +| `gdpr` | optional | GDPR Object see [Prebid.js doc](https://docs.prebid.org/dev-docs/modules/consentManagement.html) | `{}` | `Object` | +| `usp` | optional | USP Object see [Prebid.js doc](https://docs.prebid.org/dev-docs/modules/consentManagementUsp.html) | `{}` | `Object` | + + +### Example Banner ad unit +```javascript +var adUnits = [{ + code: 'banner-prebid-test-site', + mediaTypes: { + banner: { + sizes: [ + [300, 600], + [300, 250] + ] + } + }, + bids: [{ + bidder: 'aidem', + params: { + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', + }, + }] +}]; +``` + +### Example Video ad unit +```javascript +var adUnits = [{ + code: 'video-prebid-test-site', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + maxduration: 30, + minduration: 5, + mimes: ["video/mp4"], + protocols: 2 + } + }, + bids: [{ + bidder: 'aidem', + params: { + siteId: 'prebid-test-siteId', + publisherId: 'prebid-test-publisherId', + }, + }] +}]; +``` + +### Example GDPR Consent Management +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function (){ + pbjs.setConfig({ + consentManagement: { + gdpr:{ + cmpApi: 'iab' + } + } + }); +}) +``` + + +### Example USP Consent Management +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function (){ + pbjs.setConfig({ + consentManagement: { + usp:{ + cmpApi: 'static', + consentData:{ + getUSPData:{ + uspString: '1YYY' + } + } + } + } + }); +}) +``` + + +### Setting First Party Data (FPD) +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function (){ + pbjs.setConfig({ + ortb2: { + site: { + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + keywords: 'power tools, drills' + }, + } + }); +}) +``` + +### Supported Media Types +| Type | Support | +|--------|--------------------------------------------------------------------| +| Banner | Support all [AIDEM Sizes](https://kb.aidem.com/ssp/lists/adsizes/) | +| Video | Support all [AIDEM Sizes](https://kb.aidem.com/ssp/lists/adsizes/) | + + +# Setup / Dev Guide +```shell +nvm use + +npm install + +gulp build --modules=aidemBidAdapter + +gulp serve --modules=aidemBidAdapter + +# Open a chrome browser with no ad blockers enabled, and paste in this URL. The `pbjs_debug=true` is needed if you want to enable `loggerInfo` output on the `console` tab of Chrome Developer Tools. +http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true +``` + +If you need to run the tests suite but do *not* want to have to build the full adapter and serve it, simply run: +```shell +gulp test --file "test/spec/modules/aidemBidAdapter_spec.js" +``` + + +For video: gulp serve --modules=aidemBidAdapter,dfpAdServerVideo + +# FAQs +### How do I view AIDEM bid request? +Navigate to a page where AIDEM is setup to bid. In the network tab, +search for requests to `zero.aidemsrv.com/bid/request`. diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 121dc2ef96f..67b448eb484 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,4 +1,4 @@ -import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError, logWarn } from '../src/utils.js'; +import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError, logWarn, deepAccess } from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; @@ -64,6 +64,11 @@ export const spec = { })) } + const sua = deepAccess(bidRequest, 'ortb2.device.sua'); + if (sua) { + queryString = tryAppendQueryString(queryString, 'sua', JSON.stringify(sua)); + } + bidRequests.push({ method: 'GET', url: URL, diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index fe5a050f436..a96b245d786 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -20,18 +20,20 @@ export const spec = { let bidIds = []; let eids; validBidRequests.forEach(bidRequest => { - let sizes = prepareSizes(bidRequest.sizes) + let formatType = getFormatType(bidRequest) + let alkimiSizes = prepareAlkimiSizes(bidRequest.sizes) if (bidRequest.userIdAsEids) { eids = eids || bidRequest.userIdAsEids } + bids.push({ token: bidRequest.params.token, pos: bidRequest.params.pos, - bidFloor: bidRequest.params.bidFloor, - width: sizes[0].width, - height: sizes[0].height, - impMediaType: getFormatType(bidRequest), + bidFloor: getBidFloor(bidRequest, formatType), + width: alkimiSizes[0].width, + height: alkimiSizes[0].height, + impMediaType: formatType, adUnitCode: bidRequest.adUnitCode }) bidIds.push(bidRequest.bidId) @@ -128,10 +130,25 @@ export const spec = { } } -function prepareSizes(sizes) { +function prepareAlkimiSizes(sizes) { return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); } +function prepareBidFloorSize(sizes) { + return sizes && sizes.length === 1 ? sizes[0] : '*'; +} + +function getBidFloor(bidRequest, formatType) { + if (typeof bidRequest.getFloor === 'function') { + const bidFloorSize = prepareBidFloorSize(bidRequest.sizes) + const floor = bidRequest.getFloor({ currency: 'USD', mediaType: formatType.toLowerCase(), size: bidFloorSize }); + if (floor && !isNaN(floor.floor) && (floor.currency === 'USD')) { + return floor.floor; + } + } + return bidRequest.params.bidFloor; +} + const getFormatType = bidRequest => { if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner' if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video' diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index d1bd2edeee4..c7bc99b6aa6 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -207,6 +207,7 @@ const isTrue = (boolValue) => export const spec = { code: BIDDER_CODE, + gvlid: 737, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid(bid) { diff --git a/modules/amxIdSystem.md b/modules/amxIdSystem.md index 9de93c761a1..f67fefe261e 100644 --- a/modules/amxIdSystem.md +++ b/modules/amxIdSystem.md @@ -1,6 +1,6 @@ -# AMX RTB ID +# AMX ID -For help adding this module, please contact [prebid@amxrtb.com](prebid@amxrtb.com). +For help adding this module, please contact [info@amxdt.net](info@amxdt.net). ### Prebid Configuration @@ -41,11 +41,3 @@ The following settings are available for the `storage` property in the `userSync | name | Required | String | Where the ID will be stored | `"amxId"` | | type | Required | String | This must be `"html5"` | `"html5"` | | expires | Required | Number <= 30 | number of days until the stored ID expires. **Must be less than or equal to 30** | `14` | - -### Params - -The following options are available in the `params` property in `userSync.userIds[]`: - -| Param under `params` | Scope | Type | Description | Example | -| -------------------- | -------- | ------ | ------------------------------------------------------------------------- | ---------------- | -| tagId | Optional | String | Your AMX tagId (optional) | `cHJlYmlkLm9yZw` | diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index c9f64ab66b0..d15a5434e0c 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -31,15 +31,38 @@ const SYNC_TYPES = { }; const SUPPORTED_USER_ID_SOURCES = [ + 'admixer.net', 'adserver.org', + 'adtelligent.com', + 'akamai.com', + 'amxdt.net', + 'audigent.com', + 'britepool.com', 'criteo.com', + 'crwdcntrl.net', + 'deepintent.com', + 'epsilon.com', + 'hcn.health', 'id5-sync.com', + 'idx.lat', 'intentiq.com', + 'intimatemerger.com', 'liveintent.com', + 'liveramp.com', + 'mediawallahscript.com', + 'merkleinc.com', + 'netid.de', + 'neustar.biz', + 'nextroll.com', + 'novatiq.com', + 'parrable.com', + 'pubcid.org', 'quantcast.com', + 'tapad.com', + 'uidapi.com', 'verizonmedia.com', - 'liveramp.com', - 'yahoo.com' + 'yahoo.com', + 'zeotap.com' ]; const pubapiTemplate = template`${'host'}/pubapi/3.0/${'network'}/${'placement'}/${'pageid'}/${'sizeid'}/ADTECH;v=2;cmd=bid;cors=yes;alias=${'alias'};misc=${'misc'};${'dynamicParams'}`; @@ -136,6 +159,13 @@ export const spec = { if (bidderRequest) { consentData.gdpr = bidderRequest.gdprConsent; consentData.uspConsent = bidderRequest.uspConsent; + consentData.gppConsent = bidderRequest.gppConsent; + if (!consentData.gppConsent && bidderRequest.ortb2?.regs?.gpp) { + consentData.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } } return bids.map(bid => { @@ -350,6 +380,11 @@ export const spec = { params.us_privacy = consentData.uspConsent; } + if (consentData.gppConsent && consentData.gppConsent.gppString) { + params.gpp = consentData.gppConsent.gppString; + params.gpp_sid = consentData.gppConsent.applicableSections; + } + return params; }, parsePixelItems(pixels) { diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 7b59ce4ee3b..f354eb053b1 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -11,6 +11,7 @@ import { getMinValueFromArray, getParameterByName, getUniqueIdentifierStr, + getWindowFromDocument, isArray, isArrayOfNums, isEmpty, @@ -23,8 +24,7 @@ import { logMessage, logWarn, mergeDeep, - transformBidderParamKeywords, - getWindowFromDocument + transformBidderParamKeywords } from '../src/utils.js'; import {Renderer} from '../src/Renderer.js'; import {config} from '../src/config.js'; @@ -43,10 +43,15 @@ const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; -const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api']; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api', 'startdelay']; +const USER_PARAMS = ['age', 'externalUid', 'external_uid', 'segments', 'gender', 'dnt', 'language']; const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; +const DEBUG_QUERY_PARAM_MAP = { + 'apn_debug_dongle': 'dongle', + 'apn_debug_member_id': 'member_id', + 'apn_debug_timeout': 'debug_timeout' +}; const VIDEO_MAPPING = { playback_method: { 'unknown': 0, @@ -115,7 +120,9 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); + return !!( + (bid.params.placementId || bid.params.placement_id) || + (bid.params.member && (bid.params.invCode || bid.params.inv_code))); }, /** @@ -184,6 +191,18 @@ export const spec = { logError('AppNexus Debug Auction Cookie Error:\n\n' + e); } } else { + Object.keys(DEBUG_QUERY_PARAM_MAP).forEach(qparam => { + let qval = getParameterByName(qparam); + if (isStr(qval) && qval !== '') { + debugObj[DEBUG_QUERY_PARAM_MAP[qparam]] = qval; + debugObj.enabled = true; + } + }); + debugObj = convertTypes({ + 'member_id': 'number', + 'debug_timeout': 'number' + }, debugObj); + const debugBidRequest = find(bidRequests, hasDebug); if (debugBidRequest && debugBidRequest.debug) { debugObj = debugBidRequest.debug; @@ -287,6 +306,18 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } + if (bidderRequest?.gppConsent) { + payload.privacy = { + gpp: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest?.ortb2?.regs?.gpp) { + payload.privacy = { + gpp: bidderRequest.ortb2.regs.gpp, + gpp_sid: bidderRequest.ortb2.regs.gpp_sid + } + } + if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { // TODO: are these the correct referer values? @@ -314,20 +345,18 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - - addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); - addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); - if (bidRequests[0].userId.pubProvidedId) { - bidRequests[0].userId.pubProvidedId.forEach(ppId => { - ppId.uids.forEach(uid => { - eids.push({ source: ppId.source, id: uid.id }); - }); + bidRequests[0].userIdAsEids.forEach(eid => { + if (!eid || !eid.uids || eid.uids.length < 1) { return; } + eid.uids.forEach(uid => { + let tmp = {'source': eid.source, 'id': uid.id}; + if (eid.source == 'adserver.org') { + tmp.rti_partner = 'TDID'; + } else if (eid.source == 'uidapi.com') { + tmp.rti_partner = 'UID2'; + } + eids.push(tmp); }); - } - + }); if (eids.length) { payload.eids = eids; } @@ -407,8 +436,17 @@ export const spec = { } }, - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + function checkGppStatus(gppConsent) { + // this is a temporary measure to supress usersync in US-based GPP regions + // this logic will be revised when proper signals (akin to purpose1 from TCF2) can be determined for US GPP + if (gppConsent && Array.isArray(gppConsent.applicableSections)) { + return gppConsent.applicableSections.every(sec => typeof sec === 'number' && sec <= 5); + } + return true; + } + + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { return [{ type: 'iframe', url: 'https://acdn.adnxs.com/dmp/async_usersync.html' @@ -446,9 +484,6 @@ export const spec = { }, params); if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - if (isPopulatedArray(params.keywords)) { params.keywords.forEach(deleteValues); } @@ -460,6 +495,9 @@ export const spec = { delete params[paramKey]; } }); + + params.use_pmt_rule = (typeof params.use_payment_rule === 'boolean') ? params.use_payment_rule : false; + if (params.use_payment_rule) { delete params.use_payment_rule; } } return params; @@ -587,9 +625,8 @@ function newBid(serverBid, rtbBid, bidderRequest) { } }; - // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance if (rtbBid.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [rtbBid.adomain] }); } if (rtbBid.advertiser_id) { @@ -693,6 +730,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { displayUrl: nativeAd.displayurl, clickTrackers: nativeAd.link.click_trackers, impressionTrackers: nativeAd.impression_trackers, + video: nativeAd.video, javascriptTrackers: jsTrackers }; if (nativeAd.main_img) { @@ -733,17 +771,25 @@ function newBid(serverBid, rtbBid, bidderRequest) { function bidToTag(bid) { const tag = {}; + Object.keys(bid.params).forEach(paramKey => { + let convertedKey = convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + bid.params[convertedKey] = bid.params[paramKey]; + delete bid.params[paramKey]; + } + }); tag.sizes = transformSizes(bid.sizes); tag.primary_size = tag.sizes[0]; tag.ad_types = []; tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); + if (bid.params.placement_id) { + tag.id = parseInt(bid.params.placement_id, 10); } else { - tag.code = bid.params.invCode; + tag.code = bid.params.inv_code; } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false; + tag.allow_smaller_sizes = bid.params.allow_smaller_sizes || false; + tag.use_pmt_rule = (typeof bid.params.use_payment_rule === 'boolean') ? bid.params.use_payment_rule + : (typeof bid.params.use_pmt_rule === 'boolean') ? bid.params.use_pmt_rule : false; tag.prebid = true; tag.disable_psa = true; let bidFloor = getBidFloor(bid); @@ -760,26 +806,26 @@ function bidToTag(bid) { tag.position = (mediaTypePos === 3) ? 2 : mediaTypePos; } } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; + if (bid.params.traffic_source_code) { + tag.traffic_source_code = bid.params.traffic_source_code; } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); + if (bid.params.private_sizes) { + tag.private_sizes = transformSizes(bid.params.private_sizes); } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; + if (bid.params.supply_type) { + tag.supply_type = bid.params.supply_type; } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; + if (bid.params.pub_click) { + tag.pubclick = bid.params.pub_click; } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; + if (bid.params.ext_inv_code) { + tag.ext_inv_code = bid.params.ext_inv_code; } - if (bid.params.publisherId) { - tag.publisher_id = parseInt(bid.params.publisherId, 10); + if (bid.params.publisher_id) { + tag.publisher_id = parseInt(bid.params.publisher_id, 10); } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; + if (bid.params.external_imp_id) { + tag.external_imp_id = bid.params.external_imp_id; } let ortb2ImpKwStr = deepAccess(bid, 'ortb2Imp.ext.data.keywords'); @@ -903,6 +949,17 @@ function bidToTag(bid) { tag['video_frameworks'] = apiTmp; } break; + + case 'startdelay': + case 'placement': + const contextKey = 'context'; + if (typeof tag.video[contextKey] !== 'number') { + const placement = videoMediaType['placement']; + const startdelay = videoMediaType['startdelay']; + const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); + tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; + } + break; } }); } @@ -950,6 +1007,32 @@ function transformSizes(requestSizes) { return sizes; } +function getContextFromPlacement(ortbPlacement) { + if (!ortbPlacement) { + return; + } + + if (ortbPlacement === 2) { + return 'in-banner'; + } else if (ortbPlacement > 2) { + return 'outstream'; + } +} + +function getContextFromStartDelay(ortbStartDelay) { + if (!ortbStartDelay) { + return; + } + + if (ortbStartDelay === 0) { + return 'pre_roll'; + } else if (ortbStartDelay === -1) { + return 'mid_roll'; + } else if (ortbStartDelay === -2) { + return 'post_roll'; + } +} + function hasUserInfo(bid) { return !!bid.params.user; } @@ -1143,17 +1226,6 @@ function parseMediaType(rtbBid) { } } -function addUserId(eids, id, source, rti) { - if (id) { - if (rti) { - eids.push({ source, id, rti_partner: rti }); - } else { - eids.push({ source, id }); - } - } - return eids; -} - function getBidFloor(bid) { if (!isFn(bid.getFloor)) { return (bid.params.reserve) ? bid.params.reserve : null; diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js new file mode 100644 index 00000000000..1ad8fe27e42 --- /dev/null +++ b/modules/appushBidAdapter.js @@ -0,0 +1,188 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'appush'; +const AD_URL = 'https://hb.appush.com/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + // TODO: does the fallback make sense here? + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + } +}; + +registerBidder(spec); diff --git a/modules/appushBidAdapter.md b/modules/appushBidAdapter.md new file mode 100644 index 00000000000..7c04c3a6425 --- /dev/null +++ b/modules/appushBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Appush Bidder Adapter +Module Type: Appush Bidder Adapter +Maintainer: support@appush.com +``` + +# Description + +Connects to Appush exchange for bids. +Appush bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'appush', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'appush', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'appush', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/arcspanRtdProvider.js b/modules/arcspanRtdProvider.js new file mode 100644 index 00000000000..a7ffa059279 --- /dev/null +++ b/modules/arcspanRtdProvider.js @@ -0,0 +1,73 @@ +import { submodule } from '../src/hook.js'; +import { mergeDeep } from '../src/utils.js'; +import {loadExternalScript} from '../src/adloader.js'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'arcspan'; + +/** @type {RtdSubmodule} */ +export const arcspanSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: alterBidRequests, +}; + +function init(config, userConsent) { + if (typeof config.params.silo === 'undefined') { + return false; + } + if (typeof window.arcobj2 === 'undefined') { + var scriptUrl; + if (config.params.silo === 'test') { + scriptUrl = 'https://localhost:8080/as.js'; + } else { + scriptUrl = 'https://silo' + config.params.silo + '.p7cloud.net/as.js'; + } + loadExternalScript(scriptUrl, SUBMODULE_NAME); + } + return true; +} + +function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { + var _v1 = []; + var _v1s = []; + var _v2 = []; + var arcobj1 = window.arcobj1; + if (typeof arcobj1 != 'undefined') { + if (typeof arcobj1.page_iab_codes.text != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.text); } + if (typeof arcobj1.page_iab_codes.images != 'undefined') { _v1 = _v1.concat(arcobj1.page_iab_codes.images); } + if (typeof arcobj1.page_iab.text != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.text); } + if (typeof arcobj1.page_iab.images != 'undefined') { _v1s = _v1s.concat(arcobj1.page_iab.images); } + if (typeof arcobj1.page_iab_newcodes.text != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.text])]; } + if (typeof arcobj1.page_iab_newcodes.images != 'undefined') { _v2 = [...new Set([..._v2, ...arcobj1.page_iab_newcodes.images])]; } + + var _content = {}; + _content.data = []; + var p = {}; + p.name = 'arcspan'; + p.segment = []; + p.ext = { segtax: 6 }; + _v2.forEach(function (e) { + p.segment = p.segment.concat({ id: e }); + }); + _content.data = _content.data.concat(p); + var _ortb2 = { + site: { + name: 'arcspan', + domain: new URL(location.href).hostname, + cat: _v1, + sectioncat: _v1, + pagecat: _v1, + page: location.href, + ref: document.referrer, + keywords: _v1s.toString(), + content: _content, + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, _ortb2); + } + callback(); +} + +submodule(MODULE_NAME, arcspanSubmodule); diff --git a/modules/arcspanRtdProvider.md b/modules/arcspanRtdProvider.md new file mode 100644 index 00000000000..4aa1de02acf --- /dev/null +++ b/modules/arcspanRtdProvider.md @@ -0,0 +1,11 @@ +# Overview + +Module Name: ArcSpan Rtd Provider + +Module Type: Rtd Provider + +Maintainer: engineering@arcspan.com + +# Description + +RTD provider for ArcSpan Technologies. Contact jcabalugaz@arcspan.com for more information. diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index 9469bc6b00c..39c42f193b2 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -4,27 +4,32 @@ import { deepSetValue, getDNT, inIframe, + isArray, isFn, logWarn, parseSizesInput, tryAppendQueryString } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; -import {parseDomain} from '../src/refererDetection.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { parseDomain } from '../src/refererDetection.js'; const BIDDER_CODE = 'aso'; const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; const DEFAULT_SERVER_PATH = '/prebid/bidder'; const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const VERSION = '$prebid.version$_1.1'; const TTL = 300; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], + aliases: [ + {code: 'bcmint'} + ], isBidRequestValid: bid => { return !!bid.params && !!bid.params.zone; @@ -59,7 +64,7 @@ export const spec = { serverRequests.push({ method: 'POST', - url: getEnpoint(bidRequest), + url: getEndpoint(bidRequest), data: payload, options: { withCredentials: true, @@ -272,11 +277,9 @@ function createVideoImp(bidRequest, videoParams) { return imp; } -function getEnpoint(bidRequest) { - const serverUrl = bidRequest.params.serverUrl || DEFAULT_SERVER_URL; - const serverPath = bidRequest.params.serverPath || DEFAULT_SERVER_PATH; - - return serverUrl + serverPath + '?zid=' + bidRequest.params.zone + '&pbjs=$prebid.version$'; +function getEndpoint(bidRequest) { + const serverUrl = bidRequest.params.server || DEFAULT_SERVER_URL; + return serverUrl + DEFAULT_SERVER_PATH + '?zid=' + bidRequest.params.zone + '&pbjs=' + VERSION; } function getConsentsIds(gdprConsent) { @@ -341,6 +344,11 @@ function createBasePayload(bidRequest, bidderRequest) { deepSetValue(payload, 'user.ext.eids', eids); } + const schainData = deepAccess(bidRequest, 'schain.nodes'); + if (isArray(schainData) && schainData.length > 0) { + deepSetValue(payload, 'source.ext.schain', bidRequest.schain); + } + return payload; } diff --git a/modules/asoBidAdapter.md b/modules/asoBidAdapter.md index 32f4ebf5cef..f187389c5b5 100644 --- a/modules/asoBidAdapter.md +++ b/modules/asoBidAdapter.md @@ -14,12 +14,11 @@ For more information, please visit [Adserver.Online](https://adserver.online). # Parameters -| Name | Scope | Description | Example | Type | -|---------------|----------|-------------------------|-----------|-----------| -| `zone` | required | Zone ID | `73815` | `Integer` | -| `attr` | optional | Custom targeting params | `{keywords: ["a", "b"]}` | `Object` | - - +| Name | Scope | Description | Example | Type | +|-----------|----------|-------------------------|------------------------|------------| +| `zone` | required | Zone ID | `73815` | `Integer` | +| `attr` | optional | Custom targeting params | `{foo: ["a", "b"]}` | `Object` | +| `server` | optional | Custom bidder endpoint | `https://endpoint.url` | `String` | # Test parameters for banner ```js diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index f8a53a293de..4ee5aaec5be 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, isArray, logWarn, triggerPixel, buildUrl, logInfo, getValue, getBidIdParameter } from '../src/utils.js'; +import { deepAccess, isArray, isStr, logWarn, triggerPixel, buildUrl, logInfo, getValue, getBidIdParameter } from '../src/utils.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -40,6 +40,19 @@ export const spec = { const pageUrl = getPageUrl(bidderRequest.refererInfo, window); const gdpr = bidderRequest.gdprConsent; const firstSlot = slots[0]; + const kwdsFromRequest = firstSlot.kwds; + let keywords = []; + if (kwdsFromRequest) { + if (isArray(kwdsFromRequest)) { + keywords = kwdsFromRequest; + } else if (isStr(kwdsFromRequest)) { + if (kwdsFromRequest.indexOf(',') != -1) { + keywords = kwdsFromRequest.split(',').map((e) => { return e.trim() }); + } else { + keywords.push(kwdsFromRequest); + } + } + } const payloadObject = { at: new Date().toString(), nid: firstSlot.nid, @@ -47,12 +60,13 @@ export const spec = { pid: firstSlot.pid, url: pageUrl, lang: (window.navigator.language || window.navigator.languages[0]), - kwds: bidderRequest.ortb2?.site?.keywords || [], + kwds: keywords, dbg: false, slts: slots, is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null, }; + const payloadString = JSON.stringify(payloadObject); return { method: 'POST', @@ -129,6 +143,7 @@ function beOpRequestSlotsMaker(bid) { sizes: isArray(bannerSizes) ? bannerSizes : bid.sizes, flr: floor, pid: getValue(bid.params, 'accountId'), + kwds: getValue(bid.params, 'keywords'), nid: getValue(bid.params, 'networkId'), nptnid: getValue(bid.params, 'networkPartnerId'), bid: getBidIdParameter('bidId', bid), diff --git a/modules/bidViewability.js b/modules/bidViewability.js index 362401e6d1c..a5cab99b1a7 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -7,7 +7,7 @@ import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {isFn, logWarn, triggerPixel} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import adapterManager, {gdprDataHandler, uspDataHandler} from '../src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; import {find} from '../src/polyfill.js'; const MODULE_NAME = 'bidViewability'; @@ -44,6 +44,11 @@ export let fireViewabilityPixels = (globalModuleConfig, bid) => { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + // TODO - need to know what to set here for queryParams... + } + bid[BID_VURL_ARRAY].forEach(url => { // add '?' if not present in URL if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) { diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 127b534c989..ee814807331 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -125,7 +125,7 @@ export const buildBid = (bidResponse) => { requestId: deepAccess(bidResponse, 'extras.transaction_id'), width: deepAccess(bidResponse, `creative.${bid.mediaType}.width`) || 1, height: deepAccess(bidResponse, `creative.${bid.mediaType}.height`) || 1, - ttl: 3600, + ttl: 300, netRevenue: true, }); }; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 64b3c3a9fc8..15b4175b59a 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -9,6 +9,7 @@ const URL = 'https://brightcombid.marphezis.com/hb'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], + gvlid: 883, isBidRequestValid, buildRequests, interpretResponse, @@ -75,6 +76,26 @@ function buildRequests(bidReqs, bidderRequest) { deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(brightcomBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa') === true) { + deepSetValue(brightcomBidReq, 'regs.coppa', 1); + } + + if (bidReqs[0] && bidReqs[0].schain) { + deepSetValue(brightcomBidReq, 'source.ext.schain', bidReqs[0].schain) + } + + if (bidReqs[0] && bidReqs[0].userIdAsEids) { + deepSetValue(brightcomBidReq, 'user.ext.eids', bidReqs[0].userIdAsEids || []) + } + + if (bidReqs[0] && bidReqs[0].userId) { + deepSetValue(brightcomBidReq, 'user.ext.ids', bidReqs[0].userId || []) + } + return { method: 'POST', url: URL, @@ -103,7 +124,7 @@ function interpretResponse(serverResponse) { logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); return []; } - const { body: {id, seatbid} } = serverResponse; + const {body: {id, seatbid}} = serverResponse; try { const brightcomBidResponses = []; if (id && @@ -164,9 +185,9 @@ function _isViewabilityMeasurable(element) { return !_isIframe() && element !== null; } -function _getViewability(element, topWin, { w, h } = {}) { +function _getViewability(element, topWin, {w, h} = {}) { return getWindowTop().document.visibilityState === 'visible' - ? _getPercentInView(element, topWin, { w, h }) + ? _getPercentInView(element, topWin, {w, h}) : 0; } @@ -182,8 +203,8 @@ function _getMinSize(sizes) { return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); } -function _getBoundingBox(element, { w, h } = {}) { - let { width, height, left, top, right, bottom } = element.getBoundingClientRect(); +function _getBoundingBox(element, {w, h} = {}) { + let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); if ((width === 0 || height === 0) && w && h) { width = w; @@ -192,7 +213,7 @@ function _getBoundingBox(element, { w, h } = {}) { bottom = top + h; } - return { width, height, left, top, right, bottom }; + return {width, height, left, top, right, bottom}; } function _getIntersectionOfRects(rects) { @@ -225,16 +246,16 @@ function _getIntersectionOfRects(rects) { return bbox; } -function _getPercentInView(element, topWin, { w, h } = {}) { - const elementBoundingBox = _getBoundingBox(element, { w, h }); +function _getPercentInView(element, topWin, {w, h} = {}) { + const elementBoundingBox = _getBoundingBox(element, {w, h}); // Obtain the intersection of the element and the viewport - const elementInViewBoundingBox = _getIntersectionOfRects([ { + const elementInViewBoundingBox = _getIntersectionOfRects([{ left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight - }, elementBoundingBox ]); + }, elementBoundingBox]); let elementInViewArea, elementTotalArea; diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js new file mode 100644 index 00000000000..b7a9aa3fdc9 --- /dev/null +++ b/modules/brightcomSSPBidAdapter.js @@ -0,0 +1,323 @@ +import { + getBidIdParameter, + isArray, + getWindowTop, + getUniqueIdentifierStr, + deepSetValue, + logError, + logWarn, + createTrackPixelHtml, + getWindowSelf, + isFn, + isPlainObject, +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {ajax} from '../src/ajax.js'; + +const BIDDER_CODE = 'bcmssp'; +const URL = 'https://rt.marphezis.com/hb'; +const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' + +export const spec = { + code: BIDDER_CODE, + gvlid: 883, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidderError, + onTimeout, + onBidWon, + getUserSyncs, +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + const impressions = bidReqs.map(bid => { + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + + const bidFloor = _getBidFloor(bid); + + if (bidFloor) { + imp.bidfloor = bidFloor; + } + + return imp; + }) + + const referrer = bidderRequest?.refererInfo?.page || ''; + const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); + + const payload = { + id: getUniqueIdentifierStr(), + imp: impressions, + site: { + domain: bidderRequest?.refererInfo?.domain || '', + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(), + w: screen.width, + h: screen.height + }, + tmax: config.getConfig('bidderTimeout') + }; + + if (bidderRequest?.gdprConsent) { + deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest?.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (config.getConfig('coppa') === true) { + deepSetValue(payload, 'regs.coppa', 1); + } + + if (bidReqs?.[0]?.schain) { + deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) + } + + if (bidReqs?.[0]?.userIdAsEids) { + deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) + } + + if (bidReqs?.[0].userId) { + deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) + } + + return { + method: 'POST', + url: URL, + data: JSON.stringify(payload), + options: {contentType: 'text/plain', withCredentials: false} + }; + } catch (e) { + logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + let response = []; + if (!serverResponse.body || typeof serverResponse.body != 'object') { + logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return response; + } + + const {body: {id, seatbid}} = serverResponse; + + try { + if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { + response = seatbid[0].bid.map(bid => { + return { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(bid), + ttl: 60, + meta: { + advertiserDomains: bid?.adomain || [] + } + }; + }); + } + } catch (e) { + logError(e, {id, seatbid}); + } + + return response; +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function onTimeout(timeoutData) { + if (timeoutData === null) { + return; + } + + _trackEvent('timeout', timeoutData); +} + +function onBidderError(errorData) { + if (errorData === null || !errorData.bidderRequest) { + return; + } + + _trackEvent('error', errorData.bidderRequest) +} + +function onBidWon(bid) { + if (bid === null) { + return; + } + + _trackEvent('bidwon', bid) +} + +function _trackEvent(endpoint, data) { + ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { + method: 'POST', + withCredentials: false + }); +} + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, {w, h} = {}) { + return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, {w, h}) : 0; +} + +function _isIframe() { + try { + return getWindowSelf() !== getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBoundingBox(element, {w, h} = {}) { + let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return {width, height, left, top, right, bottom}; +} + +function _getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +function _getPercentInView(element, topWin, {w, h} = {}) { + const elementBoundingBox = _getBoundingBox(element, {w, h}); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = _getIntersectionOfRects([{ + left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight + }, elementBoundingBox]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} + +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', mediaType: '*', size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +registerBidder(spec); diff --git a/modules/brightcomSSPBidAdapter.md b/modules/brightcomSSPBidAdapter.md new file mode 100644 index 00000000000..8d0e4ec70dc --- /dev/null +++ b/modules/brightcomSSPBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: Brightcom SSP Bid Adapter +Module Type: Bidder Adapter +Maintainer: alexandruc@brightcom.com +``` + +# Description + +Brightcom's adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'bcmssp', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 0996fc9905b..31b2d709f35 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -59,10 +59,12 @@ export function addBrowsiTag(data) { export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { - vendor: 'browsi', - type: 'pageview', - billingId: generateUUID() + window.addEventListener('browsi_pageview', () => { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + vendor: 'browsi', + type: 'pageview', + billingId: generateUUID() + }) }) } } diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index fcf99179993..7b6c3911ea1 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js'; const WHO = 'BKSHBID-005'; const BIDDER_CODE = 'bucksense'; -const URL = 'https://prebid.bksn.se/prebidjs/'; +const URL = 'https://directo.prebidserving.com/prebidjs/'; export const spec = { code: BIDDER_CODE, diff --git a/modules/captifyRtdProvider.js b/modules/captifyRtdProvider.js new file mode 100644 index 00000000000..b97aa49a48e --- /dev/null +++ b/modules/captifyRtdProvider.js @@ -0,0 +1,146 @@ +/** + * This module adds Captify real time data provider module + * The {@link module:modules/realTimeData} module is required + * The module will fetch segments (page-centric) from Captify live-classification server + * @module modules/captifyRtdProvider + * @requires module:modules/realTimeData + */ +import { submodule } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import {deepAccess, isArray, isEmptyStr, isNumber, logError} from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'CaptifyRTDModule'; +const DEFAULT_LC_URL = 'https://live-classification.cpx.to/prebid-segments'; + +const STATUS = { + SUCCESS: 200, + ACCEPTED: 202, +}; + +/** + * Set `appnexusAuctionKeywords` that appnexus bidder will read and send in request to Xandr + * @param {Array} segments captify segments for Appnexus(Xandr) system, in form of [id1, id2, id3] + * where id1, id2, id3 - actual Xandr segment ids with keywords enabled + */ +export function setAppnexusSegments(segments) { + config.setConfig({ + appnexusAuctionKeywords: { + 'captify_segments': segments, + }, + }) +} + +/** + * Function returns only bidders that contained both, in moduleConfig and at least one adUnit. + * @param {Array} bidders Contains list of bidders, to set targeting for + * @param {Object} data Response of live-classification service + */ +export function addSegmentData(bidders, data) { + for (const bidder of bidders) { + if (bidder === 'appnexus') { setAppnexusSegments(data['xandr']) } + } +} + +/** + * Function returns only bidders that contained in both, moduleConfig and at least one adUnit. + * @param {Object} moduleConfig Config object passed to the module + * @param {Object} reqBidsConfigObj Config object for the bidders; each adapter has its own entry + */ +export function getMatchingBidders(moduleConfig, reqBidsConfigObj) { + const biddersFromConf = deepAccess(moduleConfig, 'params.bidders'); + + const adUnitBidders = reqBidsConfigObj.adUnits + .flatMap(({bids}) => bids.map(({bidder}) => bidder)) + .filter((e, i, a) => a.indexOf(e) === i); + + if (!isArray(adUnitBidders) || !adUnitBidders.length) { + logError(SUBMODULE_NAME, 'Missing parameter bidders in bidRequestConfig'); + return []; + } + + return biddersFromConf.filter(bidder => adUnitBidders.includes(bidder)); +} + +/** + * Main function that sets Captify targeting for various bidders + * @param {Object} moduleConfig Config object passed to the module + * @param {Function} onDone callback function that executed when everything is done + * @param {Object} reqBidsConfigObj Config object for the bidders; each adapter has its own entry + * @param {Object} gcv contains data related to user consent, if applies + */ +export function setCaptifyTargeting(reqBidsConfigObj, onDone, moduleConfig, gcv) { + const pbjsVer = getGlobal(); + const ref = getRefererInfo().referer; + const url = document.URL; + const pubId = moduleConfig.params.pubId; + const bidders = getMatchingBidders(moduleConfig, reqBidsConfigObj) + const requestBody = { + pubId, + ref, + url, + pbjsVer, + gcv + }; + let requestUrl = moduleConfig.params.url; + if (!requestUrl || isEmptyStr(requestUrl)) { + requestUrl = DEFAULT_LC_URL; + } + + if (!bidders.length) { + logError(SUBMODULE_NAME, 'There are no matched bidders to work with'); + return; + } + + ajax(requestUrl, { + success: function (response, req) { + if (req.status === STATUS.SUCCESS) { + try { + const data = JSON.parse(response); + if (data) { + addSegmentData(bidders, data); + } + } catch (e) { + logError(SUBMODULE_NAME, 'Unable to parse live-classification data' + e); + } + } + onDone(); + }, + error: function () { + onDone(); + logError(SUBMODULE_NAME, 'Unable to get live-classification data'); + } + }, + JSON.stringify(requestBody), + { + contentType: 'application/json', + method: 'POST', + }); +} + +export function init(moduleConfig, userConsent) { + // Validate bidders + const biddersFromConf = deepAccess(moduleConfig, 'params.bidders'); + if (!isArray(biddersFromConf) || !biddersFromConf.length) { + logError(SUBMODULE_NAME, 'Missing parameter bidders in moduleConfig'); + return false + } + const publisherId = deepAccess(moduleConfig, 'params.pubId'); + // Publisher Id + if (!isNumber(publisherId)) { + logError(SUBMODULE_NAME, 'Missing parameter pubId in moduleConfig'); + return false + } + return true +} + +export const captifySubmodule = { + name: SUBMODULE_NAME, + init: init, + setCaptifyTargeting +}; + +submodule(MODULE_NAME, captifySubmodule); diff --git a/modules/captifyRtdProvider.md b/modules/captifyRtdProvider.md new file mode 100644 index 00000000000..a1a8e9c273f --- /dev/null +++ b/modules/captifyRtdProvider.md @@ -0,0 +1,68 @@ +# Captify Real-Time Data Submodule + +# Overview + + Module Name: Captify Rtd Provider + Module Type: Rtd Provider + Layout: integrationExamples/gpt/captifyRtdProvider_example.html + Maintainer: prebid@captify.tech + +# Description + +Captify uses publisher first-party on-site search data to power machine learning algorithms to create a suite of +contextual based targeting solutions that activate in a cookieless environment. + +The RTD submodule allows bid requests to be classified by our live-classification service on the first ad call, +maximising value for publishers by increasing scale for advertisers. + +Segments will be attached to bid request objects sent to different SSPs in order to optimize targeting. + +Contact prebid@captify.tech for information. + +### Publisher Usage + +Compile the Captify RTD module into your Prebid build: + +`npm ci && gulp build --modules=rtdModule,appnexusBidAdapter,captifyRtdProvider` + +Add the Captify RTD provider to your Prebid config. + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "CaptifyRTDModule", + waitForIt: true, + params: { + pubId: 123456, + bidders: ['appnexus'], + } + } + ] + } +}); +``` + +### Parameter Description +This module is configured as part of the `realTimeData.dataProviders` object. + +| Name |Type | Description |Mandatory | Notes | +| :------------- | :------------ | :------------------------------------------------------------------ |:---------|:------------ | +| name | String | Real time data module name | yes | Always 'CaptifyRTDModule' | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (recommended) | no | Default `false` | +| params | Object | | | | +| params.pubId | Integer | Partner ID, required to get results and provided by Captify | yes | Use 123456 for tests and speak to your Captify account manager to receive your pubId | +| params.bidders | Array | List of bidders for which you would like data to be set | yes | Currently only 'appnexus' supported | +| params.url | String | Captify live-classification service url | no | Defaults to `https://live-classification.cpx.to/prebid-segments` + +### Testing + +To view an example of available segments returned by Captify: + +`gulp serve --modules=rtdModule,captifyRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/captifyRtdProvider_example.html?pbjs_debug=true` diff --git a/modules/categoryTranslation.js b/modules/categoryTranslation.js index 57267095ff0..ba73aa01b85 100644 --- a/modules/categoryTranslation.js +++ b/modules/categoryTranslation.js @@ -34,13 +34,13 @@ export const registerAdserver = hook('async', function(adServer) { }, 'registerAdserver'); registerAdserver(); -export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation', function getAdserverCategoryHook(fn, adUnitCode, bid) { +export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation', function getAdserverCategoryHook(fn, adUnitCode, bid, reject) { if (!bid) { - return fn.call(this, adUnitCode); // if no bid, call original and let it display warnings + return fn.call(this, adUnitCode, bid, reject); // if no bid, call original and let it display warnings } if (!config.getConfig('adpod.brandCategoryExclusion')) { - return fn.call(this, adUnitCode, bid); + return fn.call(this, adUnitCode, bid, reject); } let localStorageKey = (config.getConfig('brandCategoryTranslation.translationFile')) ? DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB : DEFAULT_IAB_TO_FW_MAPPING_KEY; @@ -63,7 +63,7 @@ export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation logError('Translation mapping data not found in local storage'); } } - fn.call(this, adUnitCode, bid); + fn.call(this, adUnitCode, bid, reject); }); export function initTranslation(url, localStorageKey) { diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 7c6b0411023..28f3fe3a166 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -5,6 +5,7 @@ import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'ccx' const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BID_URL = 'https://delivery.clickonometrics.pl/ortb/prebid/bid' +const GVLID = 773; const SUPPORTED_VIDEO_PROTOCOLS = [2, 3, 5, 6] const SUPPORTED_VIDEO_MIMES = ['video/mp4', 'video/x-flv'] const SUPPORTED_VIDEO_PLAYBACK_METHODS = [1, 2, 3, 4] @@ -19,8 +20,7 @@ function _getDeviceObj () { function _getSiteObj (bidderRequest) { let site = {} - // TODO: does the fallback to window.location make sense? - let url = bidderRequest?.refererInfo?.page || window.location.href + let url = bidderRequest?.refererInfo?.page || '' if (url.length > 0) { url = url.split('?')[0] } @@ -140,6 +140,7 @@ function _buildResponse (bid, currency, ttl) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: ['banner', 'video'], isBidRequestValid: function (bid) { diff --git a/modules/chtnwBidAdapter.js b/modules/chtnwBidAdapter.js new file mode 100644 index 00000000000..5f77cec018a --- /dev/null +++ b/modules/chtnwBidAdapter.js @@ -0,0 +1,110 @@ +import { + generateUUID, + getDNT, + _each, +} from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +const ENDPOINT_URL = 'https://prebid.cht.hinet.net/api/v1'; +const BIDDER_CODE = 'chtnw'; +const COOKIE_NAME = '__htid'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +const { getConfig } = config; + +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid: function(bid = {}) { + return !!(bid && bid.params); + }, + buildRequests: function(validBidRequests = [], bidderRequest = {}) { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const chtnwId = (storage.getCookie(COOKIE_NAME) != undefined) ? storage.getCookie(COOKIE_NAME) : generateUUID(); + if (storage.cookiesAreEnabled()) { + storage.setCookie(COOKIE_NAME, chtnwId); + } + const device = getConfig('device') || {}; + device.w = device.w || window.innerWidth; + device.h = device.h || window.innerHeight; + device.ua = device.ua || navigator.userAgent; + device.dnt = getDNT() ? 1 : 0; + device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const bidParams = []; + _each(validBidRequests, function(bid) { + bidParams.push({ + bidId: bid.bidId, + placement: bid.params.placementId, + sizes: bid.sizes, + adSlot: bid.adUnitCode + }); + }); + return { + method: 'POST', + url: ENDPOINT_URL + '/request/prebid.json', + data: { + bids: bidParams, + uuid: chtnwId, + device: device, + version: { + prebid: '$prebid.version$', + adapter: '1.0.0', + }, + site: { + numIframes: bidderRequest.refererInfo?.numIframes || 0, + isAmp: bidderRequest.refererInfo?.isAmp || false, + pageUrl: bidderRequest.refererInfo?.page || '', + ref: bidderRequest.refererInfo?.ref || '', + }, + }, + bids: validBidRequests + }; + }, + interpretResponse: function(serverResponse) { + const bidResponses = [] + _each(serverResponse.body, function(response, i) { + bidResponses.push({ + ...response + }); + }); + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + if (syncOptions.pixelEnabled) { + const chtnwId = generateUUID() + const uuid = chtnwId + const type = (_isMobile()) ? 'dot' : 'pixel'; + syncs.push({ + type: 'image', + url: `https://t.ssp.hinet.net/${type}?bd=${uuid}&t=chtnw` + }) + } + return syncs + }, + onTimeout: function(timeoutData) { + if (timeoutData === null) { + return; + } + ajax(ENDPOINT_URL + '/trace/timeout/bid', null, JSON.stringify(timeoutData), { + method: 'POST', + withCredentials: false + }); + }, + onBidWon: function(bid) { + if (bid.nurl) { + ajax(bid.nurl, null); + } + }, + onSetTargeting: function(bid) { + }, +} +registerBidder(spec); diff --git a/modules/chtnwBidAdapter.md b/modules/chtnwBidAdapter.md new file mode 100644 index 00000000000..08e634d577d --- /dev/null +++ b/modules/chtnwBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: CHT Bidder Adapter +Module Type: Bidder Adapter +Maintainer: chtdsp@cht.com.tw +``` + +# Description + +Module that connects to CHT's demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'chtnw', + params: { + placementId: '38EL412LO82XR9O6' + } + }] + } +]; +``` diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index c1560680a6e..082fb0ac4db 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -87,6 +87,11 @@ export const spec = { logMessage(e); } + const firstPartyData = bidderRequest.ortb2 || {}; + const userObj = firstPartyData.user; + const siteObj = firstPartyData.site; + const appObj = firstPartyData.app; + // TODO: does the fallback to window.location make sense? const location = refferLocation || winLocation; let placements = []; @@ -97,6 +102,9 @@ export const spec = { secure: location.protocol === 'https:' ? 1 : 0, host: location.host, page: location.pathname, + userObj, + siteObj, + appObj, placements: placements }; @@ -214,7 +222,7 @@ export const spec = { }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'html' : 'hms.gif'; + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let syncUrl = G_URL_SYNC + `/${syncType}?pbjs=1`; if (gdprConsent && gdprConsent.consentString) { if (typeof gdprConsent.gdprApplies === 'boolean') { diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index fc6fc23c26d..dcea60d5231 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, logMessage, debugTurnedOn, generateUUID } from '../src/utils.js'; +import { logWarn, logMessage, debugTurnedOn, generateUUID, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.js'; @@ -34,6 +34,9 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { logMessage(validBidRequests); logMessage(bidderRequest); + + const eids = []; + let payload = { meta: { prebidVersion: '$prebid.version$', @@ -49,6 +52,10 @@ export const spec = { }; payload.slots = validBidRequests.map(bidRequest => { + collectEid(eids, bidRequest); + const adUnitElement = document.getElementById(bidRequest.adUnitCode) + const coordinates = getOffset(adUnitElement) + let slot = { name: bidRequest.adUnitCode, bidId: bidRequest.bidId, @@ -59,12 +66,15 @@ export const spec = { adSlot: bidRequest.params.slot || bidRequest.adUnitCode, placementId: bidRequest.params.placementId || '', site: bidRequest.params.site || bidderRequest.refererInfo.page, - ref: bidderRequest.refererInfo.ref - }; + ref: bidderRequest.refererInfo.ref, + offsetCoordinates: { x: coordinates?.left, y: coordinates?.top } + } return slot; }); + payload.meta.eids = eids.filter(Boolean); + logMessage(payload); return { @@ -168,7 +178,7 @@ export const spec = { registerBidder(spec); -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** * Check or generate a UID for the current user. @@ -178,10 +188,24 @@ function getUid(bidderRequest) { return false; } - const CONCERT_UID_KEY = 'c_uid'; + const sharedId = deepAccess(bidderRequest, 'userId._sharedid.id'); + + if (sharedId) { + return sharedId; + } + + const LEGACY_CONCERT_UID_KEY = 'c_uid'; + const CONCERT_UID_KEY = 'vmconcert_uid'; + const legacyUid = storage.getDataFromLocalStorage(LEGACY_CONCERT_UID_KEY); let uid = storage.getDataFromLocalStorage(CONCERT_UID_KEY); + if (legacyUid) { + uid = legacyUid; + storage.setDataInLocalStorage(CONCERT_UID_KEY, uid); + storage.removeDataFromLocalStorage(LEGACY_CONCERT_UID_KEY); + } + if (!uid) { uid = generateUUID(); storage.setDataInLocalStorage(CONCERT_UID_KEY, uid); @@ -216,3 +240,35 @@ function consentAllowsPpid(bidderRequest) { return (uspConsent || gdprConsent); } + +function collectEid(eids, bid) { + if (bid.userId) { + const eid = getUserId(bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com', undefined, 3) + eids.push(eid) + } +} + +function getUserId(id, source, uidExt, atype) { + if (id) { + const uid = { id, atype }; + + if (uidExt) { + uid.ext = uidExt; + } + + return { + source, + uids: [ uid ] + }; + } +} + +function getOffset(el) { + if (el) { + const rect = el.getBoundingClientRect(); + return { + left: rect.left + window.scrollX, + top: rect.top + window.scrollY + }; + } +} diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js new file mode 100644 index 00000000000..2c13ad87d75 --- /dev/null +++ b/modules/confiantRtdProvider.js @@ -0,0 +1,128 @@ +/** + * This module provides comprehensive detection of security, quality, and privacy threats by Confiant Inc, + * the industry leader in real-time detecting and blocking of bad ads + * + * The {@link module:modules/realTimeData} module is required + * The module will inject a Confiant Inc. script into the page to monitor ad impressions + * @module modules/confiantRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { logError, generateUUID } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +/** + * Injects the Confiant Inc. configuration script into the page, based on proprtyId provided + * @param {string} propertyId + */ +function injectConfigScript(propertyId) { + const scriptSrc = `https://cdn.confiant-integrations.net/${propertyId}/gpt_and_prebid/config.js`; + + loadExternalScript(scriptSrc, 'confiant', () => {}); +} + +/** + * Set up page with Confiant integration + * @param {Object} config + */ +function setupPage(config) { + const propertyId = config?.params?.propertyId; + if (!propertyId) { + logError('Confiant pbjs module: no propertyId provided'); + return false; + } + + const confiant = window.confiant || Object.create(null); + confiant[propertyId] = confiant[propertyId] || Object.create(null); + confiant[propertyId].clientSettings = confiant[propertyId].clientSettings || Object.create(null); + confiant[propertyId].clientSettings.isMGBL = true; + confiant[propertyId].clientSettings.prebidExcludeBidders = config?.params?.prebidExcludeBidders; + confiant[propertyId].clientSettings.prebidNameSpace = config?.params?.prebidNameSpace; + + if (config?.params?.shouldEmitBillableEvent) { + if (window.frames['cnftComm']) { + subscribeToConfiantCommFrame(window); + } else { + setUpMutationObserver(); + } + } + + injectConfigScript(propertyId); + return true; +} + +/** + * Subscribe to window's message events to report Billable events + * @param {Window} targetWindow window instance to subscribe to + */ +function subscribeToConfiantCommFrame(targetWindow) { + targetWindow.addEventListener('message', reportBillableEvents); +} + +let mutationObserver; +/** + * Set up mutation observer to subscribe to Confiant's communication channel ASAP + */ +function setUpMutationObserver() { + mutationObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((addedNode) => { + if (addedNode.nodeName === 'IFRAME' && addedNode.name === 'cnftComm' && !addedNode.pbjsModuleSubscribed) { + addedNode.pbjsModuleSubscribed = true; + mutationObserver.disconnect(); + mutationObserver = null; + const iframeWindow = addedNode.contentWindow; + subscribeToConfiantCommFrame(iframeWindow); + } + }); + }); + }); + mutationObserver.observe(document.head, { childList: true, subtree: true }); +} + +/** + * Emit billable event when Confiant integration reports that it has monitored an impression + */ +function reportBillableEvents (e) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + auctionId: e.data.auctionId, + billingId: generateUUID(), + transactionId: e.data.transactionId, + type: 'impression', + vendor: 'confiant' + }); +} + +/** + * Confiant submodule registration + */ +function registerConfiantSubmodule() { + submodule('realTimeData', { + name: 'confiant', + init: (config) => { + try { + return setupPage(config); + } catch (err) { + logError(err.message); + if (mutationObserver) { + mutationObserver.disconnect(); + } + return false; + } + } + }); +} + +registerConfiantSubmodule(); + +export default { + injectConfigScript, + setupPage, + subscribeToConfiantCommFrame, + setUpMutationObserver, + reportBillableEvents, + registerConfiantSubmodule +}; diff --git a/modules/confiantRtdProvider.md b/modules/confiantRtdProvider.md new file mode 100644 index 00000000000..e92c0aabcba --- /dev/null +++ b/modules/confiantRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: Confiant Inc. Rtd provider +Module Type: Rtd Provider +Maintainer: +``` + +Confiant’s module provides comprehensive detection of security, quality, and privacy threats across your ad stack. +Confiant is the industry leader in real-time detecting and blocking of bad ads when it comes to protecting your users and brand reputation. + +To start using this module, please contact [Confiant](https://www.confiant.com/contact) to get an account and customer key. + + +# Integration + +1) Build Prebid bundle with Confiant module included: + + +``` +gulp build --modules=confiantRtdProvider,... +``` + +2) Include the resulting bundle on your page. + +# Configuration + +Configuration of Confiant module is plain simple: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'confiant', + params: { + // so please get in touch with us so we could help you to set up the module with proper parameters + propertyId: '', // required, string param, obtained from Confiant Inc. + prebidExcludeBidders: '', // optional, comma separated list of bidders to exclude from Confiant's prebid.js integration + prebidNameSpace: '', // optional, string param, namespace for prebid.js integration + shouldEmitBillableEvent: false, // optional, boolean param, upon being set to true enables firing of the BillableEvent upon Confiant's impression scanning + } + }] + } +}); +``` diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 2da2eda4c77..38c9c00b0ed 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -7,17 +7,15 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; -import {formatQS, logError} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; +import {formatQS, logError} from '../src/utils.js'; const MODULE_NAME = 'connectId'; const VENDOR_ID = 25; const PLACEHOLDER = '__PIXEL_ID__'; const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; - -function isEUConsentRequired(consentData) { - return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); -} +const OVERRIDE_OPT_OUT_KEY = 'connectIdOptOut'; +const INPUT_PARAM_KEYS = ['pixelId', 'he', 'puid']; /** @type {Submodule} */ export const connectIdSubmodule = { @@ -36,6 +34,9 @@ export const connectIdSubmodule = { * @returns {{connectId: string} | undefined} */ decode(value) { + if (connectIdSubmodule.userHasOptedOut()) { + return undefined; + } return (typeof value === 'object' && value.connectid) ? {connectId: value.connectid} : undefined; }, @@ -47,25 +48,35 @@ export const connectIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config, consentData) { + if (connectIdSubmodule.userHasOptedOut()) { + return; + } const params = config.params || {}; - if (!params || typeof params.he !== 'string' || - (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { - logError('The connectId submodule requires the \'he\' and \'pixelId\' parameters to be defined.'); + if (!params || (typeof params.he !== 'string' && typeof params.puid !== 'string') || + (typeof params.pixelId === 'undefined' && typeof params.endpoint === 'undefined')) { + logError('The connectId submodule requires the \'pixelId\' and at least one of the \'he\' ' + + 'or \'puid\' parameters to be defined.'); return; } const data = { '1p': includes([1, '1', true], params['1p']) ? '1' : '0', - he: params.he, - gdpr: isEUConsentRequired(consentData) ? '1' : '0', - gdpr_consent: isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', + gdpr: connectIdSubmodule.isEUConsentRequired(consentData) ? '1' : '0', + gdpr_consent: connectIdSubmodule.isEUConsentRequired(consentData) ? consentData.gdpr.consentString : '', us_privacy: consentData && consentData.uspConsent ? consentData.uspConsent : '' }; - if (params.pixelId) { - data.pixelId = params.pixelId + if (connectIdSubmodule.isUnderGPPJurisdiction(consentData)) { + data.gpp = consentData.gppConsent.gppString; + data.gpp_sid = encodeURIComponent(consentData.gppConsent.applicableSections.join(',')); } + INPUT_PARAM_KEYS.forEach(key => { + if (typeof params[key] != 'undefined') { + data[key] = params[key]; + } + }); + const resp = function (callback) { const callbacks = { success: response => { @@ -91,6 +102,37 @@ export const connectIdSubmodule = { return {callback: resp}; }, + /** + * Utility function that returns a boolean flag indicating if the opportunity + * is subject to GDPR + * @returns {Boolean} + */ + isEUConsentRequired(consentData) { + return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); + }, + + /** + * Utility function that returns a boolean flag indicating if the opportunity + * is subject to GPP jurisdiction. + * @returns {Boolean} + */ + isUnderGPPJurisdiction(consentData) { + return !!(consentData && consentData.gppConsent && consentData.gppConsent.gppString); + }, + + /** + * Utility function that returns a boolean flag indicating if the user + * has opeted out via the Yahoo easy-opt-out mechanism. + * @returns {Boolean} + */ + userHasOptedOut() { + try { + return localStorage.getItem(OVERRIDE_OPT_OUT_KEY) === '1'; + } catch { + return false; + } + }, + /** * Return the function used to perform XHR calls. * Utilised for each of testing. diff --git a/modules/connectIdSystem.md b/modules/connectIdSystem.md index f2153e1b6cb..c671c036b77 100644 --- a/modules/connectIdSystem.md +++ b/modules/connectIdSystem.md @@ -29,5 +29,6 @@ The below parameters apply only to the Yahoo ConnectID user ID Module. | --- | --- | --- | --- | --- | | name | Required | String | ID value for the Yahoo ConnectID module - `"connectId"` | `"connectId"` | | params | Required | Object | Data for Yahoo ConnectID initialization. | | -| params.pixelId | Required | Number | The Yahoo supplied publisher specific pixel Id | `8976` | -| params.he | Required | String | The SHA-256 hashed user email address | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | +| params.pixelId | Required | Number | The Yahoo supplied publisher specific pixel Id. | `8976` | +| params.he | Optional | String | The SHA-256 hashed user email address. One of either the `he` parameter or the `puid` parameter must be supplied. | `"529cb86de31e9547a712d9f380146e98bbd39beec"` | +| params.puid | Optional | String | The publisher-supplied user identifier. One of either the `he` parameter or the `puid` parameter must be supplied. | `"P-975484817"` | diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 3d6562958c3..bdf6649bcba 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -4,11 +4,13 @@ * and make it available for any GDPR supported adapters to read/pass this information to * their system. */ -import {isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import {gdprDataHandler} from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; +import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; +import {enrichFPD} from '../src/fpd/enrichment.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -16,12 +18,16 @@ const CMP_VERSION = 2; export let userCMP; export let consentTimeout; +export let actionTimeout; export let gdprScope; export let staticConsentData; let consentData; let addedConsentHook = false; let provisionalConsent; +let onTimeout; +let timer = null; +let actionTimer = null; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { @@ -37,6 +43,12 @@ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) } +export function setActionTimeout(timeout = setTimeout) { + clearTimeout(timer); + timer = null; + actionTimer = timeout(onTimeout, actionTimeout); +} + /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function @@ -82,6 +94,7 @@ function lookupIabConsent({onSuccess, onError}) { processCmpData(tcfData, {onSuccess, onError}); } else { provisionalConsent = tcfData; + if (!isNaN(actionTimeout) && actionTimer === null && timer != null) setActionTimeout(); } } else { onError('CMP unable to register callback function. Please check CMP setup.'); @@ -92,7 +105,7 @@ function lookupIabConsent({onSuccess, onError}) { const { cmpFrame, cmpFunction } = findCMP(); if (!cmpFrame) { - return onError('CMP not found.'); + return onError('TCF2 CMP not found.'); } // to collect the consent information from the user, we perform two calls to the CMP in parallel: // first to collect the user's consent choices represented in an encoded string (via getConsentData) @@ -160,14 +173,20 @@ function lookupIabConsent({onSuccess, onError}) { * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra * error arguments that will be undefined if there's no error. */ -function loadConsentData(cb) { +export function loadConsentData(cb, callMap = cmpCallMap, timeout = setTimeout) { let isDone = false; - let timer = null; function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { if (timer != null) { clearTimeout(timer); + timer = null; } + + if (actionTimer != null) { + clearTimeout(actionTimer); + actionTimer = null; + } + isDone = true; gdprDataHandler.setConsentData(consentData); if (typeof cb === 'function') { @@ -186,10 +205,11 @@ function loadConsentData(cb) { done(null, true, msg, ...extraArgs); } } - cmpCallMap[userCMP](callbacks); + + callMap[userCMP](callbacks); if (!isDone) { - const onTimeout = () => { + onTimeout = () => { const continueToAuction = (data) => { done(data, false, 'CMP did not load, continuing auction...'); } @@ -198,10 +218,16 @@ function loadConsentData(cb) { onError: () => continueToAuction(storeConsentData(undefined)) }) } + if (consentTimeout === 0) { onTimeout(); } else { - timer = setTimeout(onTimeout, consentTimeout); + if (timer != null) { + clearTimeout(timer); + timer = null; + } + + timer = timeout(onTimeout, consentTimeout); } } } @@ -312,11 +338,11 @@ export function resetConsentData() { * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ export function setConsentConfig(config) { - // if `config.gdpr` or `config.usp` exist, assume new config format. + // if `config.gdpr`, `config.usp` or `config.gpp` exist, assume new config format. // else for backward compatability, just use `config` - config = config && (config.gdpr || config.usp ? config.gdpr : config); + config = config && (config.gdpr || config.usp || config.gpp ? config.gdpr : config); if (!config || typeof config !== 'object') { - logWarn('consentManagement config not defined, exiting consent manager'); + logWarn('consentManagement (gdpr) config not defined, exiting consent manager'); return; } if (isStr(config.cmpApi)) { @@ -326,6 +352,10 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); } + if (isNumber(config.actionTimeout)) { + actionTimeout = config.actionTimeout; + } + if (isNumber(config.timeout)) { consentTimeout = config.timeout; } else { @@ -354,3 +384,28 @@ export function setConsentConfig(config) { loadConsentData(); // immediately look up consent data to make it available without requiring an auction } config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); + +export function enrichFPDHook(next, fpd) { + return next(fpd.then(ortb2 => { + const consent = gdprDataHandler.getConsentData(); + if (consent) { + if (typeof consent.gdprApplies === 'boolean') { + deepSetValue(ortb2, 'regs.ext.gdpr', consent.gdprApplies ? 1 : 0); + } + deepSetValue(ortb2, 'user.ext.consent', consent.consentString); + } + return ortb2; + })); +} + +enrichFPD.before(enrichFPDHook); + +export function setOrtbAdditionalConsent(ortbRequest, bidderRequest) { + // this is not a standardized name for addtlConsent, so keep this as an ORTB library processor rather than an FPD enrichment + const addtl = bidderRequest.gdprConsent?.addtlConsent; + if (addtl && typeof addtl === 'string') { + deepSetValue(ortbRequest, 'user.ext.ConsentedProvidersSettings.consented_providers', addtl); + } +} + +registerOrtbProcessor({type: REQUEST, name: 'gdprAddtlConsent', fn: setOrtbAdditionalConsent}) diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js new file mode 100644 index 00000000000..8a9c3f999f0 --- /dev/null +++ b/modules/consentManagementGpp.js @@ -0,0 +1,386 @@ +/** + * This module adds GPP consentManagement support to prebid.js. It interacts with + * supported CMPs (Consent Management Platforms) to grab the user's consent information + * and make it available for any GPP supported adapters to read/pass this information to + * their system and for various other features/modules in Prebid.js. + */ +import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {gppDataHandler} from '../src/adapterManager.js'; +import {includes} from '../src/polyfill.js'; +import {timedAuctionHook} from '../src/utils/perfMetrics.js'; +import { enrichFPD } from '../src/fpd/enrichment.js'; + +const DEFAULT_CMP = 'iab'; +const DEFAULT_CONSENT_TIMEOUT = 10000; +const CMP_VERSION = 1; + +export let userCMP; +export let consentTimeout; +export let staticConsentData; + +let consentData; +let addedConsentHook = false; + +// add new CMPs here, with their dedicated lookup function +const cmpCallMap = { + 'iab': lookupIabConsent, + 'static': lookupStaticConsentData +}; + +/** + * This function checks the state of the IAB gppData's applicableSection field (to ensure it's populated and has a valid value). + * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. + * + * TODO --- The initial version of the GPP CMP API spec used this naming convention, but it was later changed as an update to the spec. + * CMPs should adjust their logic to use the new format (applicableSecctions), but that may not be the case with the initial release. + * Added support just in case for this transition period, can likely be removed at a later date... + * @param gppData represents the IAB gppData object + * @returns true|false + */ +function checkApplicableSectionIsReady(gppData) { + return gppData && Array.isArray(gppData.applicableSection) && gppData.applicableSection.length > 0 && gppData.applicableSection[0] !== 0; +} + +/** + * This function checks the state of the IAB gppData's applicableSections field (to ensure it's populated and has a valid value). + * section === 0 represents a CMP's default value when CMP is loading, it shoud not be used a real user's section. + * @param gppData represents the IAB gppData object + * @returns true|false + */ +function checkApplicableSectionsIsReady(gppData) { + return gppData && Array.isArray(gppData.applicableSections) && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== 0; +} + +/** + * This function reads the consent string from the config to obtain the consent information of the user. + * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP + */ +function lookupStaticConsentData({onSuccess, onError}) { + processCmpData(staticConsentData, {onSuccess, onError}); +} + +/** + * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. + * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function + * based on the appropriate result. + * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP + * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + */ +function lookupIabConsent({onSuccess, onError}) { + const cmpApiName = '__gpp'; + const cmpCallbacks = {}; + let registeredPostMessageResponseListener = false; + + function findCMP() { + let f = window; + let cmpFrame; + let cmpDirectAccess = false; + while (true) { + try { + if (typeof f[cmpApiName] === 'function') { + cmpFrame = f; + cmpDirectAccess = true; + break; + } + } catch (e) {} + + // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env + try { + if (f.frames['__gppLocator']) { + cmpFrame = f; + break; + } + } catch (e) {} + + if (f === window.top) break; + f = f.parent; + } + + return { + cmpFrame, + cmpDirectAccess + }; + } + + const {cmpFrame, cmpDirectAccess} = findCMP(); + + if (!cmpFrame) { + return onError('GPP CMP not found.'); + } + + const invokeCMP = (cmpDirectAccess) ? invokeCMPDirect : invokeCMPFrame; + + function invokeCMPDirect({command, callback, parameter, version = CMP_VERSION}, resultCb) { + if (typeof resultCb === 'function') { + resultCb(cmpFrame[cmpApiName](command, callback, parameter, version)); + } else { + cmpFrame[cmpApiName](command, callback, parameter, version); + } + } + + function invokeCMPFrame({command, callback, parameter, version = CMP_VERSION}, resultCb) { + const callName = `${cmpApiName}Call`; + if (!registeredPostMessageResponseListener) { + // when we get the return message, call the stashed callback; + window.addEventListener('message', readPostMessageResponse, false); + registeredPostMessageResponseListener = true; + } + + // call CMP via postMessage + const callId = Math.random().toString(); + const msg = { + [callName]: { + command: command, + parameter, + version, + callId: callId + } + }; + + // TODO? - add logic to check if random was already used in the same session, and roll another if so? + cmpCallbacks[callId] = (typeof callback === 'function') ? callback : resultCb; + cmpFrame.postMessage(msg, '*'); + + function readPostMessageResponse(event) { + const cmpDataPkgName = `${cmpApiName}Return`; + const json = (typeof event.data === 'string' && event.data.includes(cmpDataPkgName)) ? JSON.parse(event.data) : event.data; + if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { + const payload = json[cmpDataPkgName]; + + if (cmpCallbacks.hasOwnProperty(payload.callId)) { + cmpCallbacks[payload.callId](payload.returnValue); + } + } + } + } + + const startupMsg = (cmpDirectAccess) ? 'Detected GPP CMP API is directly accessible, calling it now...' + : 'Detected GPP CMP is outside the current iframe where Prebid.js is located, calling it now...'; + logInfo(startupMsg); + + invokeCMP({ + command: 'addEventListener', + callback: function (evt) { + if (evt) { + logInfo(`Received a ${(cmpDirectAccess ? 'direct' : 'postmsg')} response from GPP CMP for event`, evt); + if (evt.eventName === 'sectionChange' || evt.pingData.cmpStatus === 'loaded') { + invokeCMP({command: 'getGPPData'}, function (gppData) { + logInfo(`Received a ${cmpDirectAccess ? 'direct' : 'postmsg'} response from GPP CMP for getGPPData`, gppData); + processCmpData(gppData, {onSuccess, onError}); + }); + } else if (evt.pingData.cmpStatus === 'error') { + onError('CMP returned with a cmpStatus:error response. Please check CMP setup.'); + } + } + } + }); +} + +/** + * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler. + * + * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra + * error arguments that will be undefined if there's no error. + */ +function loadConsentData(cb) { + let isDone = false; + let timer = null; + + function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + if (timer != null) { + clearTimeout(timer); + } + isDone = true; + gppDataHandler.setConsentData(consentData); + if (typeof cb === 'function') { + cb(shouldCancelAuction, errMsg, ...extraArgs); + } + } + + if (!includes(Object.keys(cmpCallMap), userCMP)) { + done(null, false, `GPP CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + return; + } + + const callbacks = { + onSuccess: (data) => done(data, false), + onError: function (msg, ...extraArgs) { + done(null, true, msg, ...extraArgs); + } + } + cmpCallMap[userCMP](callbacks); + + if (!isDone) { + const onTimeout = () => { + const continueToAuction = (data) => { + done(data, false, 'GPP CMP did not load, continuing auction...'); + } + processCmpData(consentData, { + onSuccess: continueToAuction, + onError: () => continueToAuction(storeConsentData(undefined)) + }) + } + if (consentTimeout === 0) { + onTimeout(); + } else { + timer = setTimeout(onTimeout, consentTimeout); + } + } +} + +/** + * Like `loadConsentData`, but cache and re-use previously loaded data. + * @param cb + */ +function loadIfMissing(cb) { + if (consentData) { + logInfo('User consent information already known. Pulling internally stored information...'); + // eslint-disable-next-line standard/no-callback-literal + cb(false); + } else { + loadConsentData(cb); + } +} + +/** + * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the + * user's encoded consent string from the supported CMP. Once obtained, the module will store this + * data as part of a gppConsent object which gets transferred to adapterManager's gppDataHandler object. + * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. + * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export const requestBidsHook = timedAuctionHook('gpp', function requestBidsHook(fn, reqBidsConfigObj) { + loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { + if (errMsg) { + let log = logWarn; + if (shouldCancelAuction) { + log = logError; + errMsg = `${errMsg} Canceling auction as per consentManagement config.`; + } + log(errMsg, ...extraArgs); + } + + if (shouldCancelAuction) { + fn.stopTiming(); + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); + } else { + logError('Error executing bidsBackHandler'); + } + } else { + fn.call(this, reqBidsConfigObj); + } + }); +}); + +/** + * This function checks the consent data provided by CMP to ensure it's in an expected state. + * If it's bad, we call `onError` + * If it's good, then we store the value and call `onSuccess` + */ +function processCmpData(consentObject, {onSuccess, onError}) { + function checkData() { + const gppString = consentObject && consentObject.gppString; + const gppSection = (checkApplicableSectionsIsReady(consentObject)) ? consentObject.applicableSections + : (checkApplicableSectionIsReady(consentObject)) ? consentObject.applicableSection : []; + + return !!( + (!Array.isArray(gppSection)) || + (Array.isArray(gppSection) && (!gppString || !isStr(gppString))) + ); + } + + if (checkData()) { + onError(`CMP returned unexpected value during lookup process.`, consentObject); + } else { + onSuccess(storeConsentData(consentObject)); + } +} + +/** + * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction + * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) + */ +function storeConsentData(cmpConsentObject) { + consentData = { + gppString: (cmpConsentObject) ? cmpConsentObject.gppString : undefined, + + fullGppData: (cmpConsentObject) || undefined, + }; + consentData.applicableSections = (checkApplicableSectionsIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSections + : (checkApplicableSectionIsReady(cmpConsentObject)) ? cmpConsentObject.applicableSection : []; + consentData.apiVersion = CMP_VERSION; + return consentData; +} + +/** + * Simply resets the module's consentData variable back to undefined, mainly for testing purposes + */ +export function resetConsentData() { + consentData = undefined; + userCMP = undefined; + consentTimeout = undefined; + gppDataHandler.reset(); +} + +/** + * A configuration function that initializes some module variables, as well as add a hook into the requestBids function + * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) + */ +export function setConsentConfig(config) { + config = config && config.gpp; + if (!config || typeof config !== 'object') { + logWarn('consentManagement.gpp config not defined, exiting consent manager module'); + return; + } + + if (isStr(config.cmpApi)) { + userCMP = config.cmpApi; + } else { + userCMP = DEFAULT_CMP; + logInfo(`consentManagement.gpp config did not specify cmp. Using system default setting (${DEFAULT_CMP}).`); + } + + if (isNumber(config.timeout)) { + consentTimeout = config.timeout; + } else { + consentTimeout = DEFAULT_CONSENT_TIMEOUT; + logInfo(`consentManagement.gpp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); + } + + if (userCMP === 'static') { + if (isPlainObject(config.consentData)) { + staticConsentData = config.consentData; + consentTimeout = 0; + } else { + logError(`consentManagement.gpp config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); + } + } + + logInfo('consentManagement.gpp module has been activated...'); + + if (!addedConsentHook) { + $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + } + addedConsentHook = true; + gppDataHandler.enable(); + loadConsentData(); // immediately look up consent data to make it available without requiring an auction +} +config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); + +export function enrichFPDHook(next, fpd) { + return next(fpd.then(ortb2 => { + const consent = gppDataHandler.getConsentData(); + if (consent) { + if (Array.isArray(consent.applicableSections)) { + deepSetValue(ortb2, 'regs.gpp_sid', consent.applicableSections); + } + deepSetValue(ortb2, 'regs.gpp', consent.gppString); + } + return ortb2; + })); +} + +enrichFPD.before(enrichFPDHook); diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 76bc7982be7..fcc13152aa9 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -4,11 +4,12 @@ * information and make it available for any USP (CCPA) supported adapters to * read/pass this information to their system. */ -import {isFn, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {deepSetValue, isFn, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; -import {uspDataHandler} from '../src/adapterManager.js'; +import adapterManager, {uspDataHandler} from '../src/adapterManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {getHook} from '../src/hook.js'; +import {enrichFPD} from '../src/fpd/enrichment.js'; const DEFAULT_CONSENT_API = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 50; @@ -99,6 +100,14 @@ function lookupUspConsent({onSuccess, onError}) { return onError('USP CMP not found.'); } + function registerDataDelHandler(invoker, arg2) { + try { + invoker('registerDeletion', arg2, adapterManager.callDataDeletionRequest); + } catch (e) { + logError('Error invoking CMP `registerDeletion`:', e); + } + } + // to collect the consent information from the user, we perform a call to USPAPI // to collect the user's consent choices represented as a string (via getUSPData) @@ -114,6 +123,7 @@ function lookupUspConsent({onSuccess, onError}) { USPAPI_VERSION, callbackHandler.consentDataCallback ); + registerDataDelHandler(uspapiFunction, USPAPI_VERSION); } else { logInfo( 'Detected USP CMP is outside the current iframe where Prebid.js is located, calling it now...' @@ -123,12 +133,13 @@ function lookupUspConsent({onSuccess, onError}) { uspapiFrame, callbackHandler.consentDataCallback ); + registerDataDelHandler(callUspApiWhileInIframe, uspapiFrame); } + let listening = false; + function callUspApiWhileInIframe(commandName, uspapiFrame, moduleCallback) { - /* Setup up a __uspapi function to do the postMessage and stash the callback. - This function behaves, from the caller's perspective, identicially to the in-frame __uspapi call (although it is not synchronous) */ - window.__uspapi = function (cmd, ver, callback) { + function callUsp(cmd, ver, callback) { let callId = Math.random() + ''; let msg = { __uspapiCall: { @@ -143,10 +154,13 @@ function lookupUspConsent({onSuccess, onError}) { }; /** when we get the return message, call the stashed callback */ - window.addEventListener('message', readPostMessageResponse, false); + if (!listening) { + window.addEventListener('message', readPostMessageResponse, false); + listening = true; + } // call uspapi - window.__uspapi(commandName, USPAPI_VERSION, moduleCallback); + callUsp(commandName, USPAPI_VERSION, moduleCallback); function readPostMessageResponse(event) { const res = event && event.data && event.data.__uspapiReturn; @@ -308,3 +322,15 @@ function enableConsentManagement(configFromUser = false) { config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); getHook('requestBids').before(requestBidsHook, 50); + +export function enrichFPDHook(next, fpd) { + return next(fpd.then(ortb2 => { + const consent = uspDataHandler.getConsentData(); + if (consent) { + deepSetValue(ortb2, 'regs.ext.us_privacy', consent) + } + return ortb2; + })) +} + +enrichFPD.before(enrichFPDHook); diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index c91f1a7f906..4e2a92fb594 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -180,12 +180,26 @@ export const spec = { return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncUrl = 'https://sync.serverbid.com/ss/' + siteId + '.html'; + if (syncOptions.iframeEnabled) { + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl = appendUrlParam(syncUrl, `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); + } else { + syncUrl = appendUrlParam(syncUrl, `gdpr=0&gdpr_consent=${gdprConsent.consentString}`); + } + } + + if (uspConsent && uspConsent.consentString) { + syncUrl = appendUrlParam(syncUrl, `us_privacy=${uspConsent.consentString}`); + } + if (!serverResponses || serverResponses.length === 0 || !serverResponses[0].body.bdr || serverResponses[0].body.bdr !== 'cx') { return [{ type: 'iframe', - url: 'https://sync.serverbid.com/ss/' + siteId + '.html' + url: syncUrl }]; } } @@ -294,4 +308,8 @@ function getBidFloor(bid, sizes) { return floor; } +function appendUrlParam(url, queryString) { + return `${url}${url.indexOf('?') > -1 ? '&' : '?'}${queryString}`; +} + registerBidder(spec); diff --git a/modules/conversantAnalyticsAdapter.md b/modules/conversantAnalyticsAdapter.md index 51db983a8bd..2f026cbcbb9 100644 --- a/modules/conversantAnalyticsAdapter.md +++ b/modules/conversantAnalyticsAdapter.md @@ -1,37 +1,36 @@ # Overview -- Module Name: Conversant Bidder Adapter +- Module Name: Epsilon Analytics Adapter - Module Type: Analytics Adapter -- Maintainer: mediapsr@conversantmedia.com +- Maintainer: mediapsr@epsilon.com ## Description -Analytics adapter for Conversant is used to track performance of Prebid auctions. See the usage below for how to +Analytics adapter for Epsilon (formerly Conversant) is used to track performance of Prebid auctions. See the usage below for how to configure the adapter for your webpage. To enable analytics and gain access to the data publishers will need - to contact their Conversant representative. + to contact their Epsilon representative (publishersupport@epsilon.com). ## Setup -Before any analytics are recorded for your website you will need to have a Conversant representative turn +Before any analytics are recorded for your website you will need to have an Epsilon representative turn on Prebid analytics for your website. -The simplest configuration to add Conversant Prebid analytics to your page is as follows: +The simplest configuration to add Epsilon Prebid analytics to your page is as follows: ``` pbjs.que.push(function() { pbjs.enableAnalytics({ provider: 'conversant', options: { - site_id: '' + site_id: } }) }); ``` -Additionally the following options are supported: +Additionally, the following options are supported: -- **sampleRate**: Expects an integer from 0 to 100. By default only 50% (corresponds to a value of 50) of instances are enabled -for Conversant Prebid Analytics. This field allows the publisher to override that percentage to sample -at a higher or lower rate. '0' would completely disable sampling and '100' would capture every instance. +- **cnvr_sampling**: Sample rate for analytics data. Value should be between 0 and 1 (inclusive), 0 == never sample, +1 == always sample, 0.5 == send analytics 50% of the time. ### Complete Example ``` @@ -39,8 +38,8 @@ at a higher or lower rate. '0' would completely disable sampling and '100' would pbjs.enableAnalytics({ provider: 'conversant', options: { - site_id: '1234', - sampleRate: 90 + site_id: 1234, + cnvr_sampling: 0.9 } }) }); diff --git a/modules/conversantBidAdapter.md b/modules/conversantBidAdapter.md index 07d9abf918b..baf8b756aca 100644 --- a/modules/conversantBidAdapter.md +++ b/modules/conversantBidAdapter.md @@ -1,12 +1,12 @@ # Overview -- Module Name: Conversant Bidder Adapter +- Module Name: Epsilon Bidder Adapter - Module Type: Bidder Adapter -- Maintainer: mediapsr@conversantmedia.com +- Maintainer: mediapsr@epsilon.com # Description -Module that connects to Conversant's demand sources. Supports banners and videos. +Module that connects to Epsilon's (formerly Conversant) demand sources. Supports banners and videos. # Test Parameters ``` diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index f3988a2fa4e..f7ba82d3e06 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -11,7 +11,7 @@ import { getRefererInfo } from '../src/refererDetection.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const GVLID = 91; -export const ADAPTER_VERSION = 34; +export const ADAPTER_VERSION = 35; const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; @@ -27,7 +27,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 130; +export const FAST_BID_VERSION_CURRENT = 135; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; @@ -523,9 +523,8 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { } }; }; - request.user = { - ext: bidderRequest.userExt - }; + request.user = bidderRequest.ortb2?.user || {}; + request.site = bidderRequest.ortb2?.site || {}; if (bidderRequest && bidderRequest.ceh) { request.user.ceh = bidderRequest.ceh; } @@ -644,26 +643,46 @@ for (var i = 0; i < 10; ++i) { `; } +function pickAvailableGetFloorFunc(bidRequest) { + if (bidRequest.getFloor) { + return bidRequest.getFloor; + } + if (bidRequest.params.bidFloor && bidRequest.params.bidFloorCur) { + try { + const floor = parseFloat(bidRequest.params.bidFloor); + return () => { + return { + currency: bidRequest.params.bidFloorCur, + floor: floor + }; + }; + } catch { } + } + return undefined; +} + function enrichSlotWithFloors(slot, bidRequest) { try { const slotFloors = {}; - if (bidRequest.getFloor) { + const getFloor = pickAvailableGetFloorFunc(bidRequest); + + if (getFloor) { if (bidRequest.mediaTypes?.banner) { slotFloors.banner = {}; const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) - bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = bidRequest.getFloor({ size: bannerSize, mediaType: BANNER })); + bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = getFloor.call(bidRequest, { size: bannerSize, mediaType: BANNER })); } if (bidRequest.mediaTypes?.video) { slotFloors.video = {}; const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) - videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = bidRequest.getFloor({ size: videoSize, mediaType: VIDEO })); + videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = getFloor.call(bidRequest, { size: videoSize, mediaType: VIDEO })); } if (bidRequest.mediaTypes?.native) { slotFloors.native = {}; - slotFloors.native['*'] = bidRequest.getFloor({ size: '*', mediaType: NATIVE }); + slotFloors.native['*'] = getFloor.call(bidRequest, { size: '*', mediaType: NATIVE }); } if (Object.keys(slotFloors).length > 0) { diff --git a/modules/currency.js b/modules/currency.js index f0ad6681afa..9272a507d05 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,11 +1,11 @@ -import { logInfo, logWarn, logError, logMessage } from '../src/utils.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { createBid } from '../src/bidfactory.js'; +import {logError, logInfo, logMessage, logWarn} from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getHook } from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getHook} from '../src/hook.js'; import {defer} from '../src/utils/promise.js'; +import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedBidResponseHook} from '../src/utils/perfMetrics.js'; const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$'; @@ -181,9 +181,9 @@ function resetCurrency() { bidderCurrencyDefault = {}; } -export const addBidResponseHook = timedBidResponseHook('currency', function addBidResponseHook(fn, adUnitCode, bid) { +export const addBidResponseHook = timedBidResponseHook('currency', function addBidResponseHook(fn, adUnitCode, bid, reject) { if (!bid) { - return fn.call(this, adUnitCode); // if no bid, call original and let it display warnings + return fn.call(this, adUnitCode, bid, reject); // if no bid, call original and let it display warnings } let bidder = bid.bidderCode || bid.bidder; @@ -209,10 +209,10 @@ export const addBidResponseHook = timedBidResponseHook('currency', function addB // execute immediately if the bid is already in the desired currency if (bid.currency === adServerCurrency) { - return fn.call(this, adUnitCode, bid); + return fn.call(this, adUnitCode, bid, reject); } - bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid])); + bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid, reject])); if (!currencySupportEnabled || currencyRatesLoaded) { processBidResponseQueue(); } else { @@ -239,7 +239,8 @@ function wrapFunction(fn, context, params) { } } catch (e) { logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); - params[1] = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers()); + // TODO: in v8, this should not continue with a "NO_BID" + params[1] = params[2](CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); } } return fn.apply(context, params); @@ -314,3 +315,11 @@ function roundFloat(num, dec) { } return Math.round(num * d) / d; } + +export function setOrtbCurrency(ortbRequest, bidderRequest, context) { + if (currencySupportEnabled) { + ortbRequest.cur = ortbRequest.cur || [context.currency || adServerCurrency]; + } +} + +registerOrtbProcessor({type: REQUEST, name: 'currency', fn: setOrtbCurrency}); diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index a11899609bc..604d7235d0f 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -1,307 +1,234 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {OUTSTREAM} from '../src/video.js'; -import { - deepAccess, - generateUUID, - getBidIdParameter, - getParameterByName, - getValue, - isArray, - isNumber, - isStr, - logError, - logWarn, - parseSizesInput, -} from '../src/utils.js'; -import {Renderer} from '../src/Renderer.js'; -import {find} from '../src/polyfill.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {generateUUID, getParameterByName, isNumber, logError, logInfo} from '../src/utils.js'; // ------------------------------------ const BIDDER_CODE = 'cwire'; -export const ENDPOINT_URL = 'https://embed.cwi.re/delivery/prebid'; -export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer.min.js'; -// ------------------------------------ -export const CW_PAGE_VIEW_ID = generateUUID(); -const LS_CWID_KEY = 'cw_cwid'; -const CW_GROUPS_QUERY = 'cwgroups'; -const CW_CREATIVE_QUERY = 'cwcreative'; +const CWID_KEY = 'cw_cwid'; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const BID_ENDPOINT = 'https://prebid.cwi.re/v1/bid'; +export const EVENT_ENDPOINT = 'https://prebid.cwi.re/v1/event'; /** - * ------------------------------------ - * ------------------------------------ - * @param bid - * @returns {Array} + * Allows limiting ad impressions per site render. Unique per prebid instance ID. */ -export function getSlotSizes(bid) { - return parseSizesInput(getAllMediaSizes(bid)); -} +export const pageViewId = generateUUID(); + +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); /** - * ------------------------------------ - * ------------------------------------ + * Retrieve dimensions and CSS max height/width from a given slot and attach the properties to the bidRequest. * @param bid - * @returns {*[]} + * @returns {*&{cwExt: {dimensions: {width: number, height: number}, style: {maxWidth: number, maxHeight: number}}}} */ -export function getAllMediaSizes(bid) { - let playerSizes = deepAccess(bid, 'mediaTypes.video.playerSize'); - let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); - let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); - - const sizes = []; - - if (isArray(playerSizes)) { - playerSizes.forEach((s) => { - sizes.push(s); - }) - } - - if (isArray(videoSizes)) { - videoSizes.forEach((s) => { - sizes.push(s); - }) +function slotDimensions(bid) { + let adUnitCode = bid.adUnitCode; + let slotEl = document.getElementById(adUnitCode); + + if (slotEl) { + logInfo(`Slot element found: ${adUnitCode}`) + const slotW = slotEl.offsetWidth + const slotH = slotEl.offsetHeight + const cssMaxW = slotEl.style?.maxWidth; + const cssMaxH = slotEl.style?.maxHeight; + logInfo(`Slot dimensions (w/h): ${slotW} / ${slotH}`) + logInfo(`Slot Styles (maxW/maxH): ${cssMaxW} / ${cssMaxH}`) + + bid = { + ...bid, + cwExt: { + dimensions: { + width: slotW, + height: slotH, + }, + style: { + ...(cssMaxW) && { + maxWidth: cssMaxW + }, + ...(cssMaxH) && { + maxHeight: cssMaxH + } + } + } + } } + return bid +} - if (isArray(bannerSizes)) { - bannerSizes.forEach((s) => { - sizes.push(s); - }) +/** + * Extracts feature flags from a comma-separated url parameter `cwfeatures`. + * + * @returns *[] + */ +function getFeatureFlags() { + let ffParam = getParameterByName('cwfeatures') + if (ffParam) { + return ffParam.split(',') } - return sizes; + return [] } -const getQueryVariable = (variable) => { - let value = getParameterByName(variable); - if (value === '') { - value = null; +function getRefGroups() { + const groups = getParameterByName('cwgroups') + if (groups) { + return groups.split(',') } - return value; -}; + return [] +} /** - * ------------------------------------ - * ------------------------------------ - * @param validBidRequests - * @returns {*[]} + * Reads the CWID from local storage. */ -export const mapSlotsData = function(validBidRequests) { - const slots = []; - validBidRequests.forEach(bid => { - const bidObj = {}; - // get testing / debug params - let cwcreative = getValue(bid.params, 'cwcreative'); - let refgroups = getValue(bid.params, 'refgroups'); - let cwapikey = getValue(bid.params, 'cwapikey'); +function getCwid() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(CWID_KEY) : null; +} - // get the pacement and page ids - let placementId = getValue(bid.params, 'placementId'); - let pageId = getValue(bid.params, 'pageId'); - // get the rest of the auction/bid/transaction info - bidObj.auctionId = getBidIdParameter('auctionId', bid); - bidObj.adUnitCode = getBidIdParameter('adUnitCode', bid); - bidObj.bidId = getBidIdParameter('bidId', bid); - bidObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); - bidObj.placementId = placementId; - bidObj.pageId = pageId; - bidObj.mediaTypes = getBidIdParameter('mediaTypes', bid); - bidObj.transactionId = getBidIdParameter('transactionId', bid); - bidObj.sizes = getSlotSizes(bid); - bidObj.cwcreative = cwcreative; - bidObj.refgroups = refgroups; - bidObj.cwapikey = cwapikey; - slots.push(bidObj); - }); +function hasCwid() { + return storage.localStorageIsEnabled() && storage.getDataFromLocalStorage(CWID_KEY); +} - return slots; -}; +/** + * Store the CWID to local storage. + */ +function updateCwid(cwid) { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(CWID_KEY, cwid) + } else { + logInfo(`Could not set CWID ${cwid} in localstorage`); + } +} + +/** + * Extract and collect cwire specific extensions. + */ +function getCwExtension() { + const cwId = getCwid(); + const cwCreative = getParameterByName('cwcreative') + const cwGroups = getRefGroups() + const cwFeatures = getFeatureFlags(); + // Enable debug flag by passing ?cwdebug=true as url parameter. + // Note: pbjs_debug=true enables it on prebid level + // More info: https://docs.prebid.org/troubleshooting/troubleshooting-guide.html#turn-on-prebidjs-debug-messages + const debug = getParameterByName('cwdebug'); + + return { + ...(cwId) && { + cwid: cwId + }, + ...(cwGroups.length > 0) && { + refgroups: cwGroups + }, + ...(cwFeatures.length > 0) && { + featureFlags: cwFeatures + }, + ...(cwCreative) && { + cwcreative: cwCreative + }, + ...(debug) && { + debug: true + } + }; +} export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER], + /** - * Determines whether or not the given bid request is valid. + * Determines whether the given bid request is valid. * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function(bid) { - bid.params = bid.params || {}; - - if (bid.params.cwcreative && !isStr(bid.params.cwcreative)) { - logError('cwcreative must be of type string!'); - return false; - } - - if (!bid.params.placementId || !isNumber(bid.params.placementId)) { - logError('placementId not provided or invalid'); + isBidRequestValid: function (bid) { + if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { + logError('placementId not provided or not a number'); return false; } - if (!bid.params.pageId || !isNumber(bid.params.pageId)) { - logError('pageId not provided'); + if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { + logError('pageId not provided or not a number'); return false; } - return true; }, /** - * ------------------------------------ - * itterate trough slots array and try - * to extract first occurence of a given - * key, if not found - return null - * ------------------------------------ - */ - getFirstValueOrNull: function(slots, key) { - const found = slots.find((item) => { - return (typeof item[key] !== 'undefined'); - }); - - return (found) ? found[key] : null; - }, - - /** - * ------------------------------------ - * Make a server request from the - * list of BidRequests. - * ------------------------------------ - * @param {validBidRequests[]} - an array of bids + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} validBidRequests An array of bids. * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests, bidderRequest) { - let slots = []; - let referer; - try { - referer = bidderRequest?.refererInfo?.page; - slots = mapSlotsData(validBidRequests); - } catch (e) { - logWarn(e); - } + buildRequests: function (validBidRequests, bidderRequest) { + // There are more fields on the refererInfo object + let referrer = bidderRequest?.refererInfo?.page - let refgroups = []; - - const cwCreative = getQueryVariable(CW_CREATIVE_QUERY) || null; - const cwCreativeIdFromConfig = this.getFirstValueOrNull(slots, 'cwcreative'); - const refGroupsFromConfig = this.getFirstValueOrNull(slots, 'refgroups'); - const cwApiKeyFromConfig = this.getFirstValueOrNull(slots, 'cwapikey'); - const rgQuery = getQueryVariable(CW_GROUPS_QUERY); - - if (refGroupsFromConfig !== null) { - refgroups = refGroupsFromConfig.split(','); - } - - if (rgQuery !== null) { - // override if query param is present - refgroups = []; - refgroups = rgQuery.split(','); - } - - const localStorageCWID = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(LS_CWID_KEY) : null; + // process bid requests + let processed = validBidRequests + .map(bid => slotDimensions(bid)) + // Flattens the pageId and placement Id for backwards compatibility. + .map((bid) => ({...bid, pageId: bid.params?.pageId, placementId: bid.params?.placementId})); + const extensions = getCwExtension(); const payload = { - cwid: localStorageCWID, - refgroups, - cwcreative: cwCreative || cwCreativeIdFromConfig, - slots: slots, - cwapikey: cwApiKeyFromConfig, - httpRef: referer || '', - pageViewId: CW_PAGE_VIEW_ID, + slots: processed, + httpRef: referrer, + // TODO: Verify whether the auctionId and the usage of pageViewId make sense. + pageViewId: pageViewId, + sdk: { + version: '$prebid.version$' + }, + ...extensions }; - + const payloadString = JSON.stringify(payload); return { method: 'POST', - url: ENDPOINT_URL, - data: payload + url: BID_ENDPOINT, + data: payloadString, }; }, - /** * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - - try { - if (typeof bidRequest.data === 'string') { - bidRequest.data = JSON.parse(bidRequest.data); + interpretResponse: function (serverResponse, bidRequest) { + if (!hasCwid()) { + const cwid = serverResponse.body?.cwid + if (cwid) { + updateCwid(cwid); } - const serverBody = serverResponse.body; - serverBody.bids.forEach((br) => { - const bidReq = find(bidRequest.data.slots, bid => bid.bidId === br.requestId); - - let mediaType = BANNER; - - const bidResponse = { - requestId: br.requestId, - cpm: br.cpm, - bidderCode: BIDDER_CODE, - width: br.dimensions[0], - height: br.dimensions[1], - creativeId: br.creativeId, - currency: br.currency, - netRevenue: br.netRevenue, - ttl: br.ttl, - meta: { - advertiserDomains: br.adomains ? br.advertiserDomains : [], - }, - - }; - - // ------------------------------------ - // IF BANNER - // ------------------------------------ - - if (deepAccess(bidReq, 'mediaTypes.banner')) { - bidResponse.ad = br.html; - } - // ------------------------------------ - // IF VIDEO - // ------------------------------------ - if (deepAccess(bidReq, 'mediaTypes.video')) { - mediaType = VIDEO; - bidResponse.vastXml = br.vastXml; - bidResponse.videoScript = br.html; - const mediaTypeContext = deepAccess(bidReq, 'mediaTypes.video.context'); - if (mediaTypeContext === OUTSTREAM) { - const r = Renderer.install({ - id: bidResponse.requestId, - adUnitCode: bidReq.adUnitCode, - url: RENDERER_URL, - loaded: false, - config: { - ...deepAccess(bidReq, 'mediaTypes.video'), - ...deepAccess(br, 'outstream', {}) - } - }); + } - // set renderer - try { - bidResponse.renderer = r; - bidResponse.renderer.setRender(function(bid) { - if (window.CWIRE && window.CWIRE.outstream) { - window.CWIRE.outstream.renderAd(bid); - } - }); - } catch (err) { - logWarn('Prebid Error calling setRender on newRenderer', err); - } - } - } + // Rename `html` response property to `ad` as used by prebid. + const bids = serverResponse.body?.bids.map(({html, ...rest}) => ({...rest, ad: html})); + return bids || []; + }, - bidResponse.mediaType = mediaType; - bidResponses.push(bidResponse); - }); - } catch (e) { - logWarn(e); + onBidWon: function (bid) { + logInfo(`Bid won.`) + const event = { + type: 'BID_WON', + payload: { + bid: bid + } } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + }, - return bidResponses; + onBidderError: function (error, bidderRequest) { + logInfo(`Bidder error: ${error}`) + const event = { + type: 'BID_ERROR', + payload: { + error: error, + bidderRequest: bidderRequest + } + } + navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, + }; registerBidder(spec); diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index 188894cd202..9804250b906 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -1,25 +1,27 @@ # Overview -Module Name: C-WIRE Bid Adapter -Module Type: Adagio Adapter -Maintainer: publishers@cwire.ch +``` +Module Name: C-WIRE Bid Adapter +Module Type: Bidder Adapter +Maintainer: devs@cwire.com +``` ## Description -Connects to C-WIRE demand source to fetch bids. +Prebid.js Adapter for C-Wire. ## Configuration - Below, the list of C-WIRE params and where they can be set. -| Param name | Global config | AdUnit config | Type | Required | -| ---------- | ------------- | ------------- |--------| ---------| -| pageId | | x | number | YES | -| placementId | | x | number | YES | -| refgroups | | x | string | NO | -| cwcreative | | x | string | NO | -| cwapikey | | x | string | NO | +| Param name | URL parameter | AdUnit config | Type | Required | +|-------------|:-------------:|:-------------:|:--------:|:-------------:| +| pageId | | x | number | YES | +| placementId | | x | number | YES | +| cwgroups | x | | string | NO | +| cwcreative | x | | string | NO | +| cwdebug | x | | boolean | NO | +| cwfeatures | x | | string | NO | ### adUnit configuration @@ -32,17 +34,22 @@ var adUnits = [ bidder: 'cwire', mediaTypes: { banner: { - sizes: [[1, 1]], + sizes: [[400, 600]], } }, params: { pageId: 1422, // required - number placementId: 2211521, // required - number - cwcreative: '42', // optional - id of creative to force - refgroups: 'test-user', // optional - name of group or coma separated list of groups to force - cwapikey: 'api_key_xyz', // optional - api key for integration testing } }] } ]; ``` + +### URL parameters + +For debugging and testing purposes url parameters can be set. + +**Example:** + +`https://www.some-site.com/article.html?cwdebug=true&cwfeatures=feature1,feature2&cwcreative=1234` diff --git a/modules/dacIdSystem.js b/modules/dacIdSystem.js index 73b5c7420cf..856e1976bb1 100644 --- a/modules/dacIdSystem.js +++ b/modules/dacIdSystem.js @@ -5,12 +5,111 @@ * @requires module:modules/userId */ -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; +import { + logError, + logInfo, + logWarn +} from '../src/utils.js'; +import { + ajax +} from '../src/ajax.js' +import { + submodule +} from '../src/hook.js'; +import { + getStorageManager +} from '../src/storageManager.js'; export const storage = getStorageManager(); -export const cookieKey = '_a1_f'; +export const FUUID_COOKIE_NAME = '_a1_f'; +export const AONEID_COOKIE_NAME = '_a1_d'; +export const API_URL = 'https://penta.a.one.impact-ad.jp/aud'; +const COOKIES_EXPIRES = 60 * 60 * 24 * 1000; // 24h +const LOG_PREFIX = 'User ID - dacId submodule: '; + +/** + * @returns {{fuuid: string, uid: string}} - + */ +function getCookieId() { + return { + fuuid: storage.getCookie(FUUID_COOKIE_NAME), + uid: storage.getCookie(AONEID_COOKIE_NAME) + }; +} + +/** + * set uid to cookie. + * @param {string} uid - + * @returns {void} - + */ +function setAoneidToCookie(uid) { + if (uid) { + const expires = new Date(Date.now() + COOKIES_EXPIRES).toUTCString(); + storage.setCookie( + AONEID_COOKIE_NAME, + uid, + expires, + 'none' + ); + } +} + +/** + * @param {string} oid - + * @param {string} fuuid - + * @returns {string} - + */ +function getApiUrl(oid, fuuid) { + return `${API_URL}?oid=${oid}&fu=${fuuid}`; +} + +/** + * @param {string} oid - + * @param {string} fuuid - + * @returns {{callback: function}} - + */ +function fetchAoneId(oid, fuuid) { + return { + callback: (callback) => { + const ret = { + fuuid, + uid: undefined + }; + const callbacks = { + success: (response) => { + if (response) { + try { + const responseObj = JSON.parse(response); + if (responseObj.error) { + logWarn(LOG_PREFIX + 'There is no permission to use API: ' + responseObj.error); + return callback(ret); + } + if (!responseObj.uid) { + logWarn(LOG_PREFIX + 'AoneId is null'); + return callback(ret); + } + ret.uid = responseObj.uid; + setAoneidToCookie(ret.uid); + } catch (error) { + logError(LOG_PREFIX + error); + } + } + callback(ret); + }, + error: (error) => { + logError(LOG_PREFIX + error); + callback(ret); + } + }; + const apiUrl = getApiUrl(oid, fuuid); + ajax(apiUrl, callbacks, undefined, { + method: 'GET', + withCredentials: true + }); + }, + }; +} export const dacIdSystemSubmodule = { /** @@ -20,38 +119,57 @@ export const dacIdSystemSubmodule = { name: 'dacId', /** - * performs action to obtain id - * @function - * @returns { {id: {dacId: string}} | undefined } + * decode the stored id value for passing to bid requests + * @param { {fuuid: string, uid: string} } id + * @returns { {dacId: {fuuid: string, dacId: string} } | undefined } */ - getId: function() { - const newId = storage.getCookie(cookieKey); - if (!newId) { - return undefined; - } - const result = { - dacId: newId + decode(id) { + if (id && typeof id === 'object') { + return { + dacId: { + fuuid: id.fuuid, + id: id.uid + } + } } - return {id: result}; }, /** - * decode the stored id value for passing to bid requests + * performs action to obtain id * @function - * @param { {dacId: string} } value - * @returns { {dacId: {id: string} } | undefined } + * @returns { {id: {fuuid: string, uid: string}} | undefined } */ - decode: function(value) { - if (value && typeof value === 'object') { - const result = {}; - if (value.dacId) { - result.id = value.dacId - } - return {dacId: result}; + getId(config) { + const cookie = getCookieId(); + + if (!cookie.fuuid) { + logInfo(LOG_PREFIX + 'There is no fuuid in cookie') + return undefined; } - return undefined; - }, -} + if (cookie.fuuid && cookie.uid) { + logInfo(LOG_PREFIX + 'There is fuuid and AoneId in cookie') + return { + id: { + fuuid: cookie.fuuid, + uid: cookie.uid + } + }; + } + + const configParams = (config && config.params) || {}; + if (!configParams || typeof configParams.oid !== 'string') { + logWarn(LOG_PREFIX + 'oid is not defined'); + return { + id: { + fuuid: cookie.fuuid, + uid: undefined + } + }; + } + + return fetchAoneId(configParams.oid, cookie.fuuid); + } +}; submodule('userId', dacIdSystemSubmodule); diff --git a/modules/dacIdSystem.md b/modules/dacIdSystem.md index 0239b4557e9..c78b8ff2741 100644 --- a/modules/dacIdSystem.md +++ b/modules/dacIdSystem.md @@ -1,7 +1,7 @@ ## AudienceOne User ID Submodule AudienceOne ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. -Please contact D.A.Consortium Inc. before using this ID. +Please visit [https://solutions.dac.co.jp/audienceone](https://solutions.dac.co.jp/audienceone) and request your Owner ID to get started. ## Building Prebid with AudienceOne ID Support @@ -17,7 +17,10 @@ The following configuration parameters are available: pbjs.setConfig({ userSync: { userIds: [{ - name: 'dacId' + name: 'dacId', + params: { + 'oid': '55h67qm4ck37vyz5' + } }] } }); @@ -26,3 +29,5 @@ pbjs.setConfig({ | Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | The name of this module. | `"dacId"` | +| params | Required | Object | Details of module params. | | +| params.oid | Required | String | This is the Owner ID value obtained via D.A.Consortium Inc. | `"55h67qm4ck37vyz5"` | \ No newline at end of file diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index 71870d6437d..193a1723403 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains } from '../src/utils.js'; +import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains, isFn, isPlainObject } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -84,7 +84,7 @@ export const spec = { /* Generate bid request for banner adunit */ function buildBannerRequest(bidRequest, bidderRequest) { - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let adW = 0; let adH = 0; @@ -135,7 +135,7 @@ function buildNativeRequest(bidRequest, bidderRequest) { let counter = 0; let assets = []; - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let title = deepAccess(bidRequest, 'mediaTypes.native.title'); if (title && title.len) { @@ -196,7 +196,7 @@ function buildNativeRequest(bidRequest, bidderRequest) { /* Generate bid request for video adunit */ function buildVideoRequest(bidRequest, bidderRequest) { - let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + let bidFloor = getBidFloor(bidRequest); let sizeObj = getVideoAdUnitSize(bidRequest); @@ -414,6 +414,7 @@ function buildBannerResponse(bidRequest, bidResponse) { } let bidSizes = (deepAccess(bidRequest, 'mediaTypes.banner.sizes')) ? deepAccess(bidRequest, 'mediaTypes.banner.sizes') : bidRequest.sizes; bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -455,6 +456,7 @@ function buildNativeResponse(bidRequest, response) { return; } bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -507,6 +509,7 @@ function buildVideoResponse(bidRequest, response) { let context = bidRequest.mediaTypes.video.context; bidResponse.requestId = bidRequest.bidId; + bidResponse.auctionId = bidRequest.auctionId; bidResponse.transactionId = bidRequest.transactionId; bidResponse.placementCode = placementCode; bidResponse.cpm = responseCPM; @@ -630,4 +633,21 @@ function getNativeAssestObj(obj, assets) { } } +// BUILD REQUESTS: BIDFLOORS +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.bidfloor) ? bid.params.bidfloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/dchain.js b/modules/dchain.js index 79e20520e93..daf97a7551f 100644 --- a/modules/dchain.js +++ b/modules/dchain.js @@ -109,7 +109,7 @@ function isValidDchain(bid) { } } -export const addBidResponseHook = timedBidResponseHook('dchain', function addBidResponseHook(fn, adUnitCode, bid) { +export const addBidResponseHook = timedBidResponseHook('dchain', function addBidResponseHook(fn, adUnitCode, bid, reject) { const basicDchain = { ver: '1.0', complete: 0, @@ -140,7 +140,7 @@ export const addBidResponseHook = timedBidResponseHook('dchain', function addBid bid.meta.dchain = basicDchain; } - fn(adUnitCode, bid); + fn(adUnitCode, bid, reject); }); export function init() { diff --git a/modules/debugging/legacy.js b/modules/debugging/legacy.js index 15b05cded64..e83b99c5194 100644 --- a/modules/debugging/legacy.js +++ b/modules/debugging/legacy.js @@ -54,7 +54,7 @@ export function applyBidOverrides(overrideObj, bidObj, bidType, logger) { }, bidObj); } -export function addBidResponseHook(next, adUnitCode, bid) { +export function addBidResponseHook(next, adUnitCode, bid, reject) { const {overrides, logger} = this; if (bidderExcluded(overrides.bidders, bid.bidderCode)) { @@ -70,7 +70,7 @@ export function addBidResponseHook(next, adUnitCode, bid) { }); } - next(adUnitCode, bid); + next(adUnitCode, bid, reject); } export function addBidderRequestsHook(next, bidderRequests) { diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 072715b4bd6..71c9c7aa341 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -8,7 +8,7 @@ import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, bui import { config } from '../src/config.js'; import { getHook, submodule } from '../src/hook.js'; import { auctionManager } from '../src/auctionManager.js'; -import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {getPPID} from '../src/adserver.js'; @@ -119,6 +119,11 @@ export function buildDfpVideoUrl(options) { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + // TODO - need to know what to set here for queryParams... + } + if (!queryParams.ppid) { const ppid = getPPID(); if (ppid != null) { diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js index 032cb4a780a..99df3b18a39 100644 --- a/modules/dgkeywordRtdProvider.js +++ b/modules/dgkeywordRtdProvider.js @@ -41,7 +41,7 @@ export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, us } else { logMessage('[dgkeyword sub module] dgkeyword targets:', setKeywordTargetBidders); logMessage('[dgkeyword sub module] get targets from profile api start.'); - ajax(getProfileApiUrl(moduleConfig?.params?.url, moduleConfig?.params?.disableReadFpid), { + ajax(getProfileApiUrl(moduleConfig?.params?.url, moduleConfig?.params?.enableReadFpid), { success: function(response) { const res = JSON.parse(response); if (!isFinish) { @@ -96,9 +96,9 @@ export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, us } } -export function getProfileApiUrl(customeUrl, disableReadFpid) { +export function getProfileApiUrl(customeUrl, enableReadFpid) { const URL = 'https://mediaconsortium.profiles.tagger.opecloud.com/api/v1'; - const fpid = (disableReadFpid) ? '' : readFpidFromLocalStrage(); + const fpid = (enableReadFpid) ? readFpidFromLocalStrage() : ''; let url = customeUrl || URL; url = url + '?url=' + encodeURIComponent(window.location.href) + ((fpid) ? `&fpid=${fpid}` : ''); return url; diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 4f22a41cf9f..8145c9d25e7 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -12,7 +12,8 @@ let itemMaps = {}; const MEDIATYPE = [BANNER, NATIVE]; /* ----- _ss_pp_id:start ------ */ -const COOKIE_KEY_MGUID = '_ss_pp_id'; +const COOKIE_KEY_SSPPID = '_ss_pp_id'; +const COOKIE_KEY_MGUID = '__mguid_'; const NATIVERET = { id: 'id', @@ -39,7 +40,7 @@ const NATIVERET = { title: { len: 75, }, - } + }, ], plcmttype: 1, privacy: 1, @@ -58,14 +59,20 @@ const NATIVERET = { * @return {string} */ const getUserID = () => { - const i = storage.getCookie(COOKIE_KEY_MGUID); + let idd = storage.getCookie(COOKIE_KEY_SSPPID); + let idm = storage.getCookie(COOKIE_KEY_MGUID); - if (i === null) { + if (idd && !idm) { + idm = idd; + } else if (idm && !idd) { + idd = idm; + } else if (!idd && !idm) { const uuid = utils.generateUUID(); storage.setCookie(COOKIE_KEY_MGUID, uuid); + storage.setCookie(COOKIE_KEY_SSPPID, uuid); return uuid; } - return i; + return idd; }; /* ----- _ss_pp_id:end ------ */ @@ -80,7 +87,6 @@ function getKv(obj, ...keys) { let o = obj; for (let key of keys) { - // console.log(key, o); if (o && o[key]) { o = o[key]; } else { @@ -222,7 +228,7 @@ function getItems(validBidRequests, bidderRequest) { let id = '' + (i + 1); if (mediaTypes.native) { - ret = {...NATIVERET, ...{id, bidFloor}} + ret = { ...NATIVERET, ...{ id, bidFloor } }; } // banner if (mediaTypes.banner) { @@ -249,6 +255,7 @@ function getItems(validBidRequests, bidderRequest) { pos: 1, }, ext: {}, + tagid: globals['tagid'], }; } itemMaps[id] = { @@ -269,18 +276,32 @@ function getItems(validBidRequests, bidderRequest) { */ function getParam(validBidRequests, bidderRequest) { const pubcid = utils.deepAccess(validBidRequests[0], 'crumbs.pubcid'); + const sharedid = + utils.deepAccess(validBidRequests[0], 'userId.sharedid.id') || + utils.deepAccess(validBidRequests[0], 'userId.pubcid'); + const eids = validBidRequests[0].userIdAsEids || validBidRequests[0].userId; + let isMobile = getDevice() ? 1 : 0; + // input test status by Publisher. more frequently for test true req + let isTest = validBidRequests[0].params.test || 0; let auctionId = getKv(bidderRequest, 'auctionId'); let items = getItems(validBidRequests, bidderRequest); - const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); - const timeout = bidderRequest.timeout || 2000; + const domain = + utils.deepAccess(bidderRequest, 'refererInfo.domain') || document.domain; + const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); + const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); + const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); + if (items && items.length) { let c = { id: 'pp_hbjs_' + auctionId, + test: +isTest, at: 1, + bcat: globals['bcat'], + badv: globals['adv'], cur: ['USD'], device: { connectiontype: 0, @@ -289,22 +310,26 @@ function getParam(validBidRequests, bidderRequest) { ua: navigator.userAgent, language: /en/.test(navigator.language) ? 'en' : navigator.language, }, + ext: { + eids, + }, user: { buyeruid: getUserID(), - id: pubcid, + id: sharedid || pubcid, }, + eids, tmax: timeout, site: { - name: globals['media'], - domain: globals['media'], - page: location, - ref: location, + name: domain, + domain: domain, + page: page || location, + ref: referer, mobile: isMobile, cat: [], // todo publisher: { + id: globals['publisher'], // todo - id: globals['media'], - name: globals['media'], + // name: xxx }, }, imp: items, @@ -329,10 +354,19 @@ export const spec = { if (bid.params.token) { globals['token'] = bid.params.token; } - if (bid.params.media) { - globals['media'] = bid.params.media; + if (bid.params.publisher) { + globals['publisher'] = bid.params.publisher; + } + if (bid.params.tagid) { + globals['tagid'] = bid.params.tagid; + } + if (bid.params.bcat) { + globals['bcat'] = Array.isArray(bid.params.bcat) ? bid.params.bcat : []; + } + if (bid.params.badv) { + globals['badv'] = Array.isArray(bid.params.badv) ? bid.params.badv : []; } - return !!(bid.params.token && bid.params.media); + return !!(bid.params.token && bid.params.publisher && bid.params.tagid); }, /** @@ -377,8 +411,8 @@ export const spec = { nurl: getKv(bid, 'nurl'), ttl: TIME_TO_LIVE, meta: { - advertiserDomains: getKv(bid, 'adomain') || [] - } + advertiserDomains: getKv(bid, 'adomain') || [], + }, }; if (mediaType === 'native') { const adm = getKv(bid, 'adm'); @@ -424,7 +458,7 @@ export const spec = { native.impressionTrackers.push(tracker.url); break; // case 2: - // native.javascriptTrackers = ``; + // native.javascriptTrackers = ``; // break; } }); @@ -460,8 +494,8 @@ export const spec = { */ onBidWon: function (bid) { if (bid['nurl']) { - utils.triggerPixel(bid['nurl']) + utils.triggerPixel(bid['nurl']); } - } + }, }; registerBidder(spec); diff --git a/modules/discoveryBidAdapter.md b/modules/discoveryBidAdapter.md index 6e7197863a5..e951b0b7448 100644 --- a/modules/discoveryBidAdapter.md +++ b/modules/discoveryBidAdapter.md @@ -1,15 +1,15 @@ # Overview ``` -Module Name: DiscoveryDSP Bid Adapter +Module Name: discovery Bid Adapter Module Type: Bidder Adapter ``` # Description -Module that connects to popIn's demand sources +Module that connects to popIn's demand sources. -The DiscoveryDSP Bidding adapter requires setup before beginning. Please contact us at +The discovery Bidding adapter requires setup before beginning. Please contact us at # Test Parameters ``` @@ -32,7 +32,8 @@ The DiscoveryDSP Bidding adapter requires setup before beginning. Please contact bidder: "discovery", params: { token: "a1b067897e4ae093d1f94261e0ddc6c9", - media: 'test_media' // your media host + tagid: 'test_tagid', + publisher: 'test_publisher' }, }, ], @@ -45,13 +46,13 @@ The DiscoveryDSP Bidding adapter requires setup before beginning. Please contact sizes: [[300, 250]], }, }, - // Replace this object to test a new adapter! bids: [ { bidder: "discovery", params: { token: "d0f4902b616cc5c38cbe0a08676d0ed9", - media: 'test_media' // your media host + tagid: 'test_tagid', + publisher: 'test_publisher' }, }, ], diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index eb69e76a837..99f313b9484 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -354,16 +354,23 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { const syncs = []; + const consentParams = []; if (syncOptions.iframeEnabled) { let url = 'https://biddr.brealtime.com/check.html'; if (gdprConsent && typeof gdprConsent.consentString === 'string') { // add 'gdpr' only if 'gdprApplies' is defined if (typeof gdprConsent.gdprApplies === 'boolean') { - url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); } else { - url += `?gdpr_consent=${gdprConsent.consentString}`; + consentParams.push(`?gdpr_consent=${gdprConsent.consentString}`); } } + if (uspConsent && typeof uspConsent.consentString === 'string') { + consentParams.push(`usp=${uspConsent.consentString}`); + } + if (consentParams.length > 0) { + url = url + '?' + consentParams.join('&'); + } syncs.push({ type: 'iframe', url: url diff --git a/modules/enrichmentFpdModule.js b/modules/enrichmentFpdModule.js index a4d7c06d0fb..59d5d326109 100644 --- a/modules/enrichmentFpdModule.js +++ b/modules/enrichmentFpdModule.js @@ -1,190 +1,2 @@ - -/** - * This module sets default values and validates ortb2 first part data - * @module modules/firstPartyData - */ -import { timestamp, mergeDeep } from '../src/utils.js'; -import { submodule } from '../src/hook.js'; -import {getRefererInfo, parseDomain} from '../src/refererDetection.js'; -import { getCoreStorageManager } from '../src/storageManager.js'; -import {GreedyPromise} from '../src/utils/promise.js'; -import {getHighEntropySUA, getLowEntropySUA} from '../libraries/fpd/sua.js'; - -let ortb2; -let win = (window === window.top) ? window : window.top; -export const coreStorage = getCoreStorageManager('enrichmentFpd'); - -export const sua = {he: getHighEntropySUA, le: getLowEntropySUA}; - -/** - * Find the root domain - * @param {string|undefined} fullDomain - * @return {string} -*/ -export function findRootDomain(fullDomain = window.location.hostname) { - if (!coreStorage.cookiesAreEnabled()) { - return fullDomain; - } - - const domainParts = fullDomain.split('.'); - if (domainParts.length == 2) { - return fullDomain; - } - let rootDomain; - let continueSearching; - let startIndex = -2; - const TEST_COOKIE_NAME = `_rdc${Date.now()}`; - const TEST_COOKIE_VALUE = 'writeable'; - do { - rootDomain = domainParts.slice(startIndex).join('.'); - let expirationDate = new Date(timestamp() + 10 * 1000).toUTCString(); - - // Write a test cookie - coreStorage.setCookie( - TEST_COOKIE_NAME, - TEST_COOKIE_VALUE, - expirationDate, - 'Lax', - rootDomain, - undefined - ); - - // See if the write was successful - const value = coreStorage.getCookie(TEST_COOKIE_NAME, undefined); - if (value === TEST_COOKIE_VALUE) { - continueSearching = false; - // Delete our test cookie - coreStorage.setCookie( - TEST_COOKIE_NAME, - '', - 'Thu, 01 Jan 1970 00:00:01 GMT', - undefined, - rootDomain, - undefined - ); - } else { - startIndex += -1; - continueSearching = Math.abs(startIndex) <= domainParts.length; - } - } while (continueSearching); - return rootDomain; -} - -/** - * Checks for referer and if exists merges into ortb2 global data - */ -function setReferer() { - if (getRefererInfo().ref) mergeDeep(ortb2, { site: { ref: getRefererInfo().ref } }); -} - -/** - * Checks for canonical url and if exists merges into ortb2 global data - */ -function setPage() { - if (getRefererInfo().page) mergeDeep(ortb2, { site: { page: getRefererInfo().page } }); -} - -/** - * Checks for canonical url and if exists retrieves domain and merges into ortb2 global data - */ -function setDomain() { - const domain = parseDomain(getRefererInfo().page, {noLeadingWww: true}); - if (domain) { - mergeDeep(ortb2, { site: { domain: domain } }); - mergeDeep(ortb2, { site: { publisher: { domain: findRootDomain(domain) } } }); - }; -} - -/** - * Checks for screen/device width and height and sets dimensions - */ -function setDimensions() { - let width; - let height; - - try { - width = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; - height = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; - } catch (e) { - width = window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth; - height = window.innerHeight || window.document.documentElement.clientHeight || window.document.body.clientHeight; - } - - mergeDeep(ortb2, { device: { w: width, h: height } }); -} - -/** - * Scans page for meta keywords, and if exists, merges into site.keywords - */ -function setKeywords() { - let keywords; - - try { - keywords = win.document.querySelector("meta[name='keywords']"); - } catch (e) { - keywords = window.document.querySelector("meta[name='keywords']"); - } - - if (keywords && keywords.content) mergeDeep(ortb2, { site: { keywords: keywords.content.replace(/\s/g, '') } }); -} - -function setDeviceSua(hints) { - let data = Array.isArray(hints) && hints.length === 0 - ? GreedyPromise.resolve(sua.le()) - : sua.he(hints); - return data.then((sua) => { - if (sua != null) { - mergeDeep(ortb2, {device: {sua}}); - } - }) -} - -/** - * Checks the Global Privacy Control status, and if exists and is true, merges into regs.ext.gpc - */ -function setGpc() { - const gpcValue = navigator.globalPrivacyControl; - if (gpcValue) { - mergeDeep(ortb2, { regs: { ext: { gpc: 1 } } }) - } -} - -/** - * Resets modules global ortb2 data - */ -export const resetEnrichments = () => { ortb2 = null }; - -function runEnrichments(fpdConf) { - setReferer(); - setPage(); - setDomain(); - setDimensions(); - setKeywords(); - setGpc(); - return setDeviceSua(fpdConf.uaHints).then(() => ortb2); -} - -export function processFpd(fpdConf, {global}) { - if (fpdConf.skipEnrichments) { - return {global}; - } else { - let ready; - if (ortb2 == null) { - ortb2 = {}; - ready = runEnrichments(fpdConf); - } else { - ready = GreedyPromise.resolve(); - } - return ready.then(() => ({ - global: mergeDeep({}, ortb2, global) - })) - } -} -/** @type {firstPartyDataSubmodule} */ -export const enrichmentsSubmodule = { - name: 'enrichments', - queue: 2, - processFpd -} - -submodule('firstPartyData', enrichmentsSubmodule) +// Logic from this module was moved into core since approx. 7.27 +// TODO: remove this in v8 diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 0d37b72f4ad..e230858487f 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -2,6 +2,7 @@ import {getWindowSelf, isEmpty, parseSizesInput, isGptPubadsDefined, isSlotMatch import {getGlobal} from '../src/prebidGlobal.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'eplanning'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -19,9 +20,14 @@ const STORAGE_VIEW_PREFIX = 'pbvi_'; const mobileUserAgent = isMobileUserAgent(); const PRIORITY_ORDER_FOR_MOBILE_SIZES_ASC = ['1x1', '300x50', '320x50', '300x250']; const PRIORITY_ORDER_FOR_DESKTOP_SIZES_ASC = ['1x1', '970x90', '970x250', '160x600', '300x600', '728x90', '300x250']; +const VAST_INSTREAM = 1; +const VAST_OUTSTREAM = 2; +const VAST_VERSION_DEFAULT = 3; +const DEFAULT_SIZE_VAST = '640x480'; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { return Boolean(bid.params.ci) || Boolean(bid.params.t); @@ -86,6 +92,10 @@ export const spec = { params['e_' + id] = (typeof userIds[id] === 'object') ? encodeURIComponent(JSON.stringify(userIds[id])) : encodeURIComponent(userIds[id]); } } + if (spaces.impType) { + params.vctx = spaces.impType & VAST_INSTREAM ? VAST_INSTREAM : VAST_OUTSTREAM; + params.vv = VAST_VERSION_DEFAULT; + } } return { @@ -108,7 +118,6 @@ export const spec = { cpm: ad.pr, width: ad.w, height: ad.h, - ad: ad.adm, ttl: TTL, creativeId: ad.crid, netRevenue: NET_REVENUE, @@ -119,6 +128,13 @@ export const spec = { advertiserDomains: ad.adom }; } + if (request && request.data && request.data.vv) { + bidResponse.vastXml = ad.adm; + bidResponse.mediaType = VIDEO; + } else { + bidResponse.ad = ad.adm; + } + bidResponses.push(bidResponse); }); } @@ -236,17 +252,42 @@ function getSpacesStruct(bids) { return e; } +function getFirstSizeVast(sizes) { + if (sizes == undefined || !Array.isArray(sizes)) { + return undefined; + } + + let size = Array.isArray(sizes[0]) ? sizes[0] : sizes; + + return (Array.isArray(size) && size.length == 2) ? size : undefined; +} + function cleanName(name) { return name.replace(/_|\.|-|\//g, '').replace(/\)\(|\(|\)|:/g, '_').replace(/^_+|_+$/g, ''); } function getSpaces(bidRequests, ml) { + let impType = bidRequests.reduce((previousBits, bid) => (bid.mediaTypes && bid.mediaTypes[VIDEO]) ? (bid.mediaTypes[VIDEO].context == 'outstream' ? (previousBits | 2) : (previousBits | 1)) : previousBits, 0); + // Only one type of auction is supported at a time + if (impType) { + bidRequests = bidRequests.filter((bid) => bid.mediaTypes && bid.mediaTypes[VIDEO] && (impType & VAST_INSTREAM ? (!bid.mediaTypes[VIDEO].context || bid.mediaTypes[VIDEO].context == 'instream') : (bid.mediaTypes[VIDEO].context == 'outstream'))); + } + let spacesStruct = getSpacesStruct(bidRequests); - let es = {str: '', vs: '', map: {}}; + let es = {str: '', vs: '', map: {}, impType: impType}; es.str = Object.keys(spacesStruct).map(size => spacesStruct[size].map((bid, i) => { es.vs += getVs(bid); let name; + + if (impType) { + let firstSize = getFirstSizeVast(bid.mediaTypes[VIDEO].playerSize); + let sizeVast = firstSize ? firstSize.join('x') : DEFAULT_SIZE_VAST; + name = 'video_' + sizeVast + '_' + i; + es.map[name] = bid.bidId; + return name + ':' + sizeVast + ';1'; + } + if (ml) { name = cleanName(bid.adUnitCode); } else { @@ -462,4 +503,5 @@ function registerAuction(storageID) { return true; } + registerBidder(spec); diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 0ab6def38ae..34e14cd674a 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -7,7 +7,23 @@ import {ajax} from '../src/ajax.js'; * Version of the FeedAd bid adapter * @type {string} */ -const VERSION = '1.0.3'; +const VERSION = '1.0.6'; + +/** + * @typedef {object} FeedAdUserSync + * @inner + * + * @property {string} type + * @property {string} url + */ + +/** + * @typedef {object} FeedAdBidExtension + * @inner + * + * @property {FeedAdUserSync[]} pixels + * @property {FeedAdUserSync[]} iframes + */ /** * @typedef {object} FeedAdApiBidRequest @@ -16,7 +32,8 @@ const VERSION = '1.0.3'; * @property {number} ad_type * @property {string} client_token * @property {string} placement_id - * @property {string} sdk_version + * @property {string} prebid_adapter_version + * @property {string} prebid_sdk_version * @property {boolean} app_hybrid * * @property {string} [app_bundle_id] @@ -40,7 +57,7 @@ const VERSION = '1.0.3'; * @property {string} requestId - bids[].bidId * @property {number} ttl - Time to live for this ad * @property {number} width - Width of creative returned in [].ad - * @property {object} [ext] - an extension object + * @property {FeedAdBidExtension} [ext] - an extension object */ /** @@ -62,6 +79,14 @@ const VERSION = '1.0.3'; * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ +/** + * @typedef {object} FeedAdServerResponse + * @extends ServerResponse + * @inner + * + * @property {FeedAdApiBidResponse[]} body - the body of a FeedAd server response + */ + /** * The IAB TCF 2.0 vendor ID for the FeedAd GmbH */ @@ -181,7 +206,8 @@ function createApiBidRParams(request) { ad_type: 0, client_token: request.params.clientToken, placement_id: request.params.placementId, - sdk_version: `prebid_${VERSION}`, + prebid_adapter_version: VERSION, + prebid_sdk_version: '$prebid.version$', app_hybrid: false, }); } @@ -207,7 +233,6 @@ function buildRequests(validBidRequests, bidderRequest) { }) }); data.bids.forEach(bid => BID_METADATA[bid.bidId] = { - // TODO: is 'page' the right value here? referer: data.refererInfo.page, transactionId: bid.transactionId }); @@ -227,7 +252,7 @@ function buildRequests(validBidRequests, bidderRequest) { /** * Adapts the FeedAd server response to Prebid format - * @param {ServerResponse} serverResponse - the FeedAd server response + * @param {FeedAdServerResponse} serverResponse - the FeedAd server response * @param {BidRequest} request - the initial bid request * @returns {Bid[]} the FeedAd bids */ @@ -266,7 +291,8 @@ function createTrackingParams(data, klass) { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: VERSION + prebid_adapter_version: VERSION, + prebid_sdk_version: '$prebid.version$', }; } @@ -294,16 +320,20 @@ function trackingHandlerFactory(klass) { /** * Reads the user syncs off the server responses and converts them into Prebid.JS format * @param {SyncOptions} syncOptions - * @param {FeedAdApiBidResponse[]} serverResponses + * @param {FeedAdServerResponse[]} serverResponses * @param gdprConsent * @param uspConsent */ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - return serverResponses.map(response => response.ext) - .flatMap(extension => { + return serverResponses.flatMap(response => { + // merge all response bodies into one + const body = response.body; + return isArray(body) ? body : []; + }) + .flatMap(/** @param {FeedAdApiBidResponse} bidResponse */ bidResponse => { // extract user syncs from extension - const pixels = syncOptions.pixelEnabled && extension.pixels ? extension.pixels : []; - const iframes = syncOptions.iframeEnabled && extension.iframes ? extension.iframes : []; + const pixels = (syncOptions.pixelEnabled && bidResponse?.ext?.pixels) ? bidResponse.ext.pixels : []; + const iframes = (syncOptions.iframeEnabled && bidResponse?.ext?.iframes) ? bidResponse.ext.iframes : []; return pixels.concat(...iframes); }) .reduce((syncs, sync) => { diff --git a/modules/fledgeForGpt.md b/modules/fledgeForGpt.md index 5672ac04618..3bb86cd5946 100644 --- a/modules/fledgeForGpt.md +++ b/modules/fledgeForGpt.md @@ -1,34 +1,33 @@ -## Overview +# Overview +This module allows Prebid.js to support FLEDGE by integrating it with GPT's [experimental FLEDGE +support](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing). -This module enables the handling of fledge auction by GPT. -Bid adapters can now return component auction configs in addition to bids. +To learn more about FLEDGE in general, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md). -Those component auction configs will be registered to the GPT ad slot, -and GPT will be responsible to run fledge auction and render the winning fledge ad. +This document covers the steps necessary for publishers to enable FLEDGE on their inventory. It also describes +the changes Bid Adapters need to implement in order to support FLEDGE. -Find out more [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md). - -## Integration - -Build the fledge module into the Prebid.js package with: +## Publisher Integration +Publishers wishing to enable FLEDGE support must do two things. First, they must compile Prebid.js with support for this module. +This is accomplished by adding the `fledgeForGpt` module to the list of modules they are already using: ``` gulp build --modules=fledgeForGpt,... ``` -## Module Configuration Parameters +Second, they must enable FLEDGE in their Prebid.js configuration. To provide a high degree of flexiblity for testing, FLEDGE +settings exist at the module level, the bidder level, and the slot level. + +### Module Configuration +This module exposes the following settings: |Name |Type |Description |Notes | | :------------ | :------------ | :------------ |:------------ | |enabled | Boolean |Enable/disable the module |Defaults to `false` | -## Configuration +As noted above, FLEDGE support is disabled by default. To enable it, set the `enabled` value to `true` for this module +using the `setConfig` method of Prebid.js: -Fledge support need to be enabled at 3 levels: -- `fledgeForGpt` module -- bidder -- adunit -### enabling the fledgeForGpt module ```js pbjs.que.push(function() { pbjs.setConfig({ @@ -39,7 +38,17 @@ pbjs.que.push(function() { }); ``` -### enablingthe bidder +### Bidder Configuration +This module adds the following setting for bidders: + +|Name |Type |Description |Notes | +| :------------ | :------------ | :------------ |:------------ | +| fledgeEnabled | Boolean | Enable/disable a bidder to participate in FLEDGE | Defaults to `false` | + +In addition to enabling FLEDGE at the module level, individual bidders must also be enabled. This allows publishers to +selectively test with one or more bidders as they desire. To enable one or more bidders, use the `setBidderConfig` method +of Prebid.js: + ```js pbjs.setBidderConfig({ bidders: ["openx"], @@ -49,12 +58,81 @@ pbjs.setBidderConfig({ }); ``` -### enabling the adunit -ortb2Imp.ext.ae on the adunit must be set 1 +### AdUnit Configuration +Enabling an adunit for FLEDGE eligibility is accomplished by setting an attribute of the `ortb2Imp` object for that +adunit. + +|Name |Type |Description |Notes | +| :------------ | :------------ | :------------ |:------------ | +| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates FLEDGE eligible, 0 indicates it is not | Absence indicates this is not FLEDGE eligible | + +The `ae` field stands for Auction Environment and was chosen to be consistent with the field that GAM passes to bidders +in their Open Bidding and Exchange Bidding APIs. More details on that can be found +[here](https://github.com/google/ads-privacy/tree/master/proposals/fledge-rtb#bid-request-changes-indicating-interest-group-auction-support) +In practice, this looks as follows: + ```js -ortb2Imp: { - ext: { - ae: 1 +pbjs.addAdUnits({ + code: "my-adunit-div", + // other config here + ortb2Imp: { + ext: { + ae: 1 + } + } +}); +``` + +## Bid Adapter Integration +Chrome has enabled a two-tier auction in FLEDGE. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with +a single entity serving as the final decision maker. In their [current approach](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing), +GPT has opted to run the final auction layer while allowing other SSPs/sellers to participate as +[Component Auctions](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) which feed their +bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#24-scoring-bids-in-component-auctions). + +The FLEDGE auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given +seller. This module enables FLEDGE support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an +`AuctionConfig` object, Prebid.js will register it with the appropriate GPT ad slot so the bidder can participate as a Component Auction in the overall +FLEDGE auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). + +Modifying a bid adapter to support FLEDGE is a straightforward process and consists of the following steps: +1. Detecting when a bid request is FLEDGE eligible +2. Responding with AuctionConfig + +FLEDGE eligibility is made available to bid adapters through the `bidderRequest.fledgeEnabled` field. +The [`bidderRequest`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#bidderrequest-parameters) object is passed to +the [`buildRequests`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#building-the-request) method of an adapter. Bid adapters +who wish to participate should read this flag and pass it to their server. FLEDGE eligibility depends on a number of parameters: + +1. Chrome enablement +2. Publisher participatipon in the [Origin Trial](https://developer.chrome.com/docs/privacy-sandbox/unified-origin-trial/#configure) +3. Publisher Prebid.js configuration (detailed above) + +When a bid request is FLEDGE enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: + +```js +function interpretResponse(resp, req) { + // Load the bids from the response - this is adapter specific + const bids = parseBids(resp); + + // Load the auctionConfigs from the response - also adapter specific + const auctionConfigs = parseAuctionConfigs(resp); + + if (auctionConfigs) { + // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. + return {bids, auctionConfigs}; + } else { + return bids; } } ``` + +An AuctionConfig must be associated with an adunit and auction, and this is accomplished using the value in the `bidId` field from the objects in the +`validBidRequests` array passed to the `buildRequests` function - see [here](https://docs.prebid.org/dev-docs/bidder-adaptor.html#ad-unit-params-in-the-validbidrequests-array) +for more details. This means that the AuctionConfig objects returned from `interpretResponse` must contain a `bidId` field whose value corresponds to +the request it should be associated with. This may raise the question: why isn't the AuctionConfig object returned as part of the bid? The +answer is that it's possible to participate in the FLEDGE auction without returning a contextual bid. + +An example of this can be seen in the OpenX OpenRTB bid adapter [here](https://github.com/prebid/Prebid.js/blob/master/modules/openxOrtbBidAdapter.js#L327). + +Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in FLEDGE. The details of creating an AuctionConfig object are beyond the scope of this document. diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index ea634027dbe..004d44e6737 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -62,6 +62,11 @@ export const spec = { }); data.params = request.params; + + if (request.schain) { + data.schain = request.schain; + } + const searchParams = new URLSearchParams({ dfpUnitCode: request.params.dfpUnitCode, tagId: request.params.tagId, diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md index 638c966883a..238881db96b 100644 --- a/modules/fpdModule/index.md +++ b/modules/fpdModule/index.md @@ -16,9 +16,11 @@ Validation Submodule: - verify that certain OpenRTB attributes are not specified - optionally suppress user FPD based on the existence of _pubcid_optout +Topic Submodule: +- populate first party/third party topics data onto user.data in bid stream. 1. Module initializes on first load and set bidRequestHook -2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations dependant on submodule +2. When hook runs, corresponding submodule init functions are run to perform enrichments/validations/topics dependant on submodule 3. After hook complete, it is disabled - meaning module only runs on first auction 4. To reinitiate the module, run pbjs.refreshFPD(), which allows module to rerun as if initial load @@ -43,4 +45,5 @@ pbjs.setConfig({ At least one of the submodules must be included in order to successfully run the corresponding above operations. enrichmentFpdModule -validationFpdModule \ No newline at end of file +validationFpdModule +topicsFpdModule \ No newline at end of file diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 38973f915b6..b4d8f69d1b4 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -78,6 +78,39 @@ function getPricing(xmlNode) { return princingData; } +/* +* Read the StickyBrand extension with this format: +* +* +* +* +* +* +* @return {object} pricing data in format: {currency: "EUR", price:"1.000"} +*/ +function getAdvertiserDomain(xmlNode) { + var domain = []; + var brandExtNode; + var extensions = xmlNode.querySelectorAll('Extension'); + // Nodelist.forEach is not supported in IE and Edge + // Workaround given here https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10638731/ + Array.prototype.forEach.call(extensions, function(node) { + if (node.getAttribute('type') === 'StickyBrand') { + brandExtNode = node; + } + }); + + // Currently we only return one Domain + if (brandExtNode) { + var domainNode = brandExtNode.querySelector('Domain'); + domain.push(domainNode.textContent || domainNode.innerText); + } else { + logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing StickyBrand extension.'); + } + + return domain; +} + function hashcode(inputString) { var hash = 0; var char; @@ -260,7 +293,7 @@ var getOutstreamScript = function(bid) { export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], - aliases: ['stickyadstv'], // former name for freewheel-ssp + aliases: ['stickyadstv', 'freewheelssp'], // aliases for freewheel-ssp /** * Determines whether or not the given bid request is valid. * @@ -315,7 +348,11 @@ export const spec = { // Add schain object var schain = currentBidRequest.schain; if (schain) { - requestParams.schain = schain; + try { + requestParams.schain = JSON.stringify(schain); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the schain: ' + error); + } } var vastParams = currentBidRequest.params.vastUrlParams; @@ -326,7 +363,7 @@ export const spec = { } } } - // TODO: is 'page' the right value here? + var location = bidderRequest?.refererInfo?.page; if (isValidUrl(location)) { requestParams.loc = location; @@ -409,6 +446,8 @@ export const spec = { const campaignId = getCampaignId(xmlDoc); const bannerId = getBannerId(xmlDoc); const topWin = getTopMostWindow(); + const advertiserDomains = getAdvertiserDomain(xmlDoc); + if (!topWin.freewheelssp_cache) { topWin.freewheelssp_cache = {}; } @@ -426,7 +465,7 @@ export const spec = { currency: princingData.currency, netRevenue: true, ttl: 360, - meta: { advertiserDomains: princingData.adomain && isArray(princingData.adomain) ? princingData.adomain : [] }, + meta: { advertiserDomains: advertiserDomains }, dealId: dealId, campaignId: campaignId, bannerId: bannerId @@ -454,14 +493,20 @@ export const spec = { } } + const syncs = []; if (syncOptions && syncOptions.pixelEnabled) { - return [{ + syncs.push({ type: 'image', url: USER_SYNC_URL + gdprParams - }]; - } else { - return []; + }); + } else if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL + gdprParams + }); } + + return syncs; }, }; diff --git a/modules/freewheel-sspBidAdapter.md b/modules/freewheel-sspBidAdapter.md index 0086aac6567..a445280f2b0 100644 --- a/modules/freewheel-sspBidAdapter.md +++ b/modules/freewheel-sspBidAdapter.md @@ -22,7 +22,7 @@ Module that connects to Freewheel ssp's demand sources bids: [ { - bidder: "freewheel-ssp", + bidder: "freewheelssp", // or use alias "freewheel-ssp" params: { zoneId : '277225' } diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 55f04e6b1d3..244807a3164 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -48,20 +48,39 @@ export const ftrackIdSubmodule = { * similar to the module name and ending in id or Id */ decode (value, config) { - if (!value) { return } - const ext = {} + if (!value) { + return; + }; - for (var key in value) { - /** unpack the strings from the arrays */ - ext[key] = value[key][0] + const DECODE_RESPONSE = { + ftrackId: { + uid: '', + ext: {} + } } - return { - ftrackId: { - uid: value.DeviceID && value.DeviceID[0], - ext + // Loop over the value's properties: + // -- if string, assign value as is. + // -- if array, convert to string then assign value. + // -- If neither type, assign value as empty string + for (var key in value) { + let keyValue = value[key]; + if (Array.isArray(keyValue)) { + keyValue = keyValue.join('|'); + } else if (typeof value[key] !== 'string') { + // Unexpected value type, should be string or array + keyValue = ''; } + + DECODE_RESPONSE.ftrackId.ext[key] = keyValue; } + + // If we have DeviceId value, assign it to the uid property + if (DECODE_RESPONSE.ftrackId.ext.hasOwnProperty('DeviceID')) { + DECODE_RESPONSE.ftrackId.uid = DECODE_RESPONSE.ftrackId.ext.DeviceID; + } + + return DECODE_RESPONSE; }, /** diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index b7035c509b6..9553ad0586a 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -2,7 +2,7 @@ * This module gives publishers extra set of features to enforce individual purposes of TCF v2 */ -import {deepAccess, hasDeviceAccess, isArray, logWarn} from '../src/utils.js'; +import {deepAccess, hasDeviceAccess, isArray, logError, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import adapterManager, {gdprDataHandler} from '../src/adapterManager.js'; import {find, includes} from '../src/polyfill.js'; @@ -11,13 +11,7 @@ import {getHook} from '../src/hook.js'; import {validateStorageEnforcement} from '../src/storageManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; - -// modules for which vendor consent is not needed (see https://github.com/prebid/Prebid.js/issues/8161) -const VENDORLESS_MODULES = new Set([ - 'sharedId', - 'pubCommonId', - 'pubProvidedId', -]); +import {VENDORLESS_GVLID} from '../src/consentHandler.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; @@ -71,7 +65,7 @@ export const internal = { * @param {{string|Object}} - module * @return {number} - GVL ID */ -export function getGvlid(module) { +export function getGvlid(module, ...args) { let gvlid = null; if (module) { // Check user defined GVL Mapping in pbjs.setConfig() @@ -86,7 +80,7 @@ export function getGvlid(module) { return gvlid; } - gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode); + gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode, ...args); } return gvlid; } @@ -120,10 +114,19 @@ function getGvlidForUserIdModule(userIdModule) { /** * Returns GVL ID for an analytics adapter. If an analytics adapter does not have an associated GVL ID, it returns 'null'. * @param {string} code - 'provider' property on the analytics adapter config + * @param {{}} config - analytics configuration object * @return {number} GVL ID */ -function getGvlidForAnalyticsAdapter(code) { - return adapterManager.getAnalyticsAdapter(code) && (adapterManager.getAnalyticsAdapter(code).gvlid || null); +function getGvlidForAnalyticsAdapter(code, config) { + const adapter = adapterManager.getAnalyticsAdapter(code); + return adapter?.gvlid || ((gvlid) => { + if (typeof gvlid !== 'function') return gvlid; + try { + return gvlid.call(adapter.adapter, config); + } catch (e) { + logError(`Error invoking ${code} adapter.gvlid()`, e) + } + })(adapter?.adapter?.gvlid) } export function shouldEnforce(consentData, purpose, name) { @@ -145,20 +148,20 @@ export function shouldEnforce(consentData, purpose, name) { * @param {Object} consentData - gdpr consent data * @param {string=} currentModule - Bidder code of the current module * @param {number=} gvlId - GVL ID for the module - * @param vendorlessModule a predicate function that takes a module name, and returns true if the module does not need vendor consent * @returns {boolean} */ -export function validateRules(rule, consentData, currentModule, gvlId, vendorlessModule = VENDORLESS_MODULES.has.bind(VENDORLESS_MODULES)) { +export function validateRules(rule, consentData, currentModule, gvlId) { const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id; // return 'true' if vendor present in 'vendorExceptions' - if (includes(rule.vendorExceptions || [], currentModule)) { + if ((rule.vendorExceptions || []).includes(currentModule)) { return true; } + const vendorConsentRequred = !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))) // get data from the consent string const purposeConsent = deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`); - const vendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); + const vendorConsent = vendorConsentRequred ? deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`) : true; const liTransparency = deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`); /* @@ -166,7 +169,7 @@ export function validateRules(rule, consentData, currentModule, gvlId, vendorles or the user has consented. Similar with vendors. */ const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true; - const vendorAllowed = vendorlessModule(currentModule) || rule.enforceVendor === false || vendorConsent === true; + const vendorAllowed = rule.enforceVendor === false || vendorConsent === true; /* Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming @@ -183,19 +186,18 @@ export function validateRules(rule, consentData, currentModule, gvlId, vendorles /** * This hook checks whether module has permission to access device or not. Device access include cookie and local storage * @param {Function} fn reference to original function (used by hook logic) - * @param isVendorless if true, do not require vendor consent (for e.g. core modules) * @param {Number=} gvlid gvlid of the module * @param {string=} moduleName name of the module * @param result */ -export function deviceAccessHook(fn, isVendorless, gvlid, moduleName, result, {validate = validateRules} = {}) { +export function deviceAccessHook(fn, gvlid, moduleName, result, {validate = validateRules} = {}) { result = Object.assign({}, { hasEnforcementHook: true }); if (!hasDeviceAccess()) { logWarn('Device access is disabled by Publisher'); result.valid = false; - } else if (isVendorless && !strictStorageEnforcement) { + } else if (gvlid === VENDORLESS_GVLID && !strictStorageEnforcement) { // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set result.valid = true; } else { @@ -203,13 +205,13 @@ export function deviceAccessHook(fn, isVendorless, gvlid, moduleName, result, {v if (shouldEnforce(consentData, 1, moduleName)) { const curBidder = config.getCurrentBidder(); // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder - if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { + if (curBidder && (curBidder !== moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { gvlid = getGvlid(curBidder); } else { gvlid = getGvlid(moduleName) || gvlid; } const curModule = moduleName || curBidder; - let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid, isVendorless ? () => true : undefined); + let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid,); if (isAllowed) { result.valid = true; } else { @@ -221,7 +223,7 @@ export function deviceAccessHook(fn, isVendorless, gvlid, moduleName, result, {v result.valid = true; } } - fn.call(this, isVendorless, gvlid, moduleName, result); + fn.call(this, gvlid, moduleName, result); } /** @@ -314,7 +316,7 @@ export function enableAnalyticsHook(fn, config) { } config = config.filter(conf => { const analyticsAdapterCode = conf.provider; - const gvlid = getGvlid(analyticsAdapterCode); + const gvlid = getGvlid(analyticsAdapterCode, conf); const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); if (!isAllowed) { analyticsBlocked.push(analyticsAdapterCode); diff --git a/modules/genericAnalyticsAdapter.js b/modules/genericAnalyticsAdapter.js new file mode 100644 index 00000000000..4909745e72b --- /dev/null +++ b/modules/genericAnalyticsAdapter.js @@ -0,0 +1,157 @@ +import AnalyticsAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {prefixLog, isPlainObject} from '../src/utils.js'; +import * as CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager.js'; +import {ajaxBuilder} from '../src/ajax.js'; + +const DEFAULTS = { + batchSize: 1, + batchDelay: 100, + method: 'POST' +} + +const TYPES = { + handler: 'function', + batchSize: 'number', + batchDelay: 'number', + gvlid: 'number', +} + +const MAX_CALL_DEPTH = 20; + +export function GenericAnalytics() { + const parent = AnalyticsAdapter({analyticsType: 'endpoint'}); + const {logError, logWarn} = prefixLog('Generic analytics:'); + let batch = []; + let callDepth = 0; + let options, handler, timer, translate; + + function optionsAreValid(options) { + if (!options.url && !options.handler) { + logError('options must specify either `url` or `handler`') + return false; + } + if (options.hasOwnProperty('method') && !['GET', 'POST'].includes(options.method)) { + logError('options.method must be GET or POST'); + return false; + } + for (const [field, type] of Object.entries(TYPES)) { + // eslint-disable-next-line valid-typeof + if (options.hasOwnProperty(field) && typeof options[field] !== type) { + logError(`options.${field} must be a ${type}`); + return false; + } + } + if (options.hasOwnProperty('events')) { + if (!isPlainObject(options.events)) { + logError('options.events must be an object'); + return false; + } + for (const [event, handler] of Object.entries(options.events)) { + if (!CONSTANTS.EVENTS.hasOwnProperty(event)) { + logWarn(`options.events.${event} does not match any known Prebid event`); + if (typeof handler !== 'function') { + logError(`options.events.${event} must be a function`); + return false; + } + } + } + } + return true; + } + + function processBatch() { + const currentBatch = batch; + batch = []; + callDepth++; + try { + // the pub-provided handler may inadvertently cause an infinite chain of events; + // even just logging an exception from it may cause an AUCTION_DEBUG event, that + // gets back to the handler, that throws another exception etc. + // to avoid the issue, put a cap on recursion + if (callDepth === MAX_CALL_DEPTH) { + logError('detected probable infinite recursion, discarding events', currentBatch); + } + if (callDepth >= MAX_CALL_DEPTH) { + return; + } + try { + handler(currentBatch); + } catch (e) { + logError('error executing options.handler', e); + } + } finally { + callDepth--; + } + } + + function translator(eventHandlers) { + if (!eventHandlers) { + return (data) => data; + } + return function ({eventType, args}) { + if (eventHandlers.hasOwnProperty(eventType)) { + try { + return eventHandlers[eventType](args); + } catch (e) { + logError(`error executing options.events.${eventType}`, e); + } + } + } + } + + return Object.assign( + Object.create(parent), + { + gvlid(config) { + return config?.options?.gvlid + }, + enableAnalytics(config) { + if (optionsAreValid(config?.options || {})) { + options = Object.assign({}, DEFAULTS, config.options); + handler = options.handler || defaultHandler(options); + translate = translator(options.events); + parent.enableAnalytics.call(this, config); + } + }, + track(event) { + if (event.eventType === CONSTANTS.EVENTS.AUCTION_INIT && event.args.hasOwnProperty('config')) { + // clean up auctionInit event + // TODO: remove this special case in v8 + delete event.args.config; + } + const datum = translate(event); + if (datum != null) { + batch.push(datum); + if (timer != null) { + clearTimeout(timer); + timer = null; + } + if (batch.length >= options.batchSize) { + processBatch(); + } else { + timer = setTimeout(processBatch, options.batchDelay); + } + } + } + } + ) +} + +export function defaultHandler({url, method, batchSize, ajax = ajaxBuilder()}) { + const callbacks = { + success() {}, + error() {} + } + const extract = batchSize > 1 ? (events) => events : (events) => events[0]; + const serialize = method === 'GET' ? (data) => ({data: JSON.stringify(data)}) : (data) => JSON.stringify(data); + + return function (events) { + ajax(url, callbacks, serialize(extract(events)), {method}) + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: GenericAnalytics(), + code: 'generic', +}); diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 001ef67b66a..6f910632fbc 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -18,6 +18,8 @@ import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; import { generateUUID, insertElement, isEmpty, logError } from '../src/utils.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; /** @type {string} */ const SUBMODULE_NAME = 'geoedge'; @@ -105,6 +107,7 @@ function getMacros(bid, key) { '%%PATTERN:hb_bidder%%': bid.bidderCode, '%_isHb!': true, '%_hbcid!': bid.creativeId || '', + '%_hbadomains': bid.meta && bid.meta.advertiserDomains, '%%PATTERN:hb_pb%%': bid.pbHg, '%%SITE%%': location.hostname, '%_pimp%': PV_ID @@ -184,6 +187,24 @@ function conditionallyWrap(bidResponse, config, userConsent) { } } +/** + * Fire billable events for applicable bids + */ +function fireBillableEventsForApplicableBids(params) { + events.on(CONSTANTS.EVENTS.BID_WON, function (winningBid) { + if (shouldWrap(winningBid, params)) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + vendor: SUBMODULE_NAME, + billingId: generateUUID(), + type: 'impression', + transactionId: winningBid.transactionId, + auctionId: winningBid.auctionId, + bidId: winningBid.requestId + }); + } + }); +} + function init(config, userConsent) { let params = config.params; if (!params || !params.key) { @@ -191,6 +212,7 @@ function init(config, userConsent) { return false; } preloadClient(params.key); + fireBillableEventsForApplicableBids(params); return true; } diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js new file mode 100644 index 00000000000..ae3407bbbdd --- /dev/null +++ b/modules/globalsunBidAdapter.js @@ -0,0 +1,212 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'globalsun'; +const AD_URL = 'https://endpoint.globalsun.io/pbjs'; +const SYNC_URL = 'https://cs.globalsun.io'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + // TODO: does the fallback make sense here? + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/globalsunBidAdapter.md b/modules/globalsunBidAdapter.md new file mode 100644 index 00000000000..07c3ce32155 --- /dev/null +++ b/modules/globalsunBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Globalsun Bidder Adapter +Module Type: Globalsun Bidder Adapter +Maintainer: prebid@globalsun.io +``` + +# Description + +Connects to Globalsun exchange for bids. +Globalsun bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'globalsun', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'globalsun', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'globalsun', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/glomexBidAdapter.js b/modules/glomexBidAdapter.js index 32c2036a748..5c9b3c1fa28 100644 --- a/modules/glomexBidAdapter.js +++ b/modules/glomexBidAdapter.js @@ -4,9 +4,11 @@ import {BANNER} from '../src/mediaTypes.js'; const ENDPOINT = 'https://prebid.mes.glomex.cloud/request-bid' const BIDDER_CODE = 'glomex' +const GVLID = 967 export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER], isBidRequestValid: function (bid) { diff --git a/modules/gravitoIdSystem.js b/modules/gravitoIdSystem.js index 30fa3abd6d2..809263a1c68 100644 --- a/modules/gravitoIdSystem.js +++ b/modules/gravitoIdSystem.js @@ -39,19 +39,18 @@ export const gravitoIdSystemSubmodule = { * decode the stored id value for passing to bid requests * @function * @param { {gravitompId: string} } value - * @returns { {gravitompId: {id: string} } | undefined } + * @returns { {gravitompId: {string} } | undefined } */ decode: function(value) { if (value && typeof value === 'object') { - const result = {}; + var result = {}; if (value.gravitompId) { - result.id = value.gravitompId + result = value.gravitompId } return {gravitompId: result}; } return undefined; }, - } submodule('userId', gravitoIdSystemSubmodule); diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index e46d6e352fa..ce7fcc680dd 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -1,18 +1,38 @@ -import { isEmpty, deepAccess, logError, parseGPTSingleSizeArrayToRtbSize, generateUUID, mergeDeep, logWarn } from '../src/utils.js'; +import { + isEmpty, + deepAccess, + logError, + parseGPTSingleSizeArrayToRtbSize, + generateUUID, + mergeDeep, + logWarn, + parseUrl, isArray, isNumber +} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import { find } from '../src/polyfill.js'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; + +const ADAPTER_VERSION_FOR_CRITEO_MODE = 34; +const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; +const PROFILE_ID_INLINE = 207; +const SID_COOKIE_NAME = 'cto_sid'; +const IDCPY_COOKIE_NAME = 'cto_idcpy'; +const OPTOUT_COOKIE_NAME = 'cto_optout'; +const BUNDLE_COOKIE_NAME = 'cto_bundle'; + const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; const USER_ID_KEY = 'tmguid'; const GVLID = 686; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); + const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -39,6 +59,7 @@ let hasSynced = false; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['playwire', 'adlivetech', { code: 'trustx', skipPbsAliasing: true }], supportedMediaTypes: [ BANNER, VIDEO ], /** @@ -48,7 +69,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - return !!bid.params.uid; + return bid && Boolean(bid.params.uid || bid.params.secid); }, /** * Make a server request from the list of BidRequests. @@ -73,15 +94,24 @@ export const spec = { let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - const bidderTimeout = config.getConfig('bidderTimeout') || timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + const tmax = timeout || config.getConfig('bidderTimeout'); const imp = []; const bidsMap = {}; const requests = []; + const criteoBidsMap = {}; + const criteoBidRequests = []; const sources = []; const bidsArray = []; validBidRequests.forEach((bid) => { + const bidObject = { bid, savedPrebidBid: null }; + if (bid.params.withCriteo && criteoSpec.isBidRequestValid(bid)) { + criteoBidsMap[bid.bidId] = bidObject; + criteoBidRequests.push(bid); + } + if (!bid.params.uid && !bid.params.secid) { + return; + } if (!bidderRequestId) { bidderRequestId = bid.bidderRequestId; } @@ -99,7 +129,6 @@ export const spec = { } const { params: { uid, keywords, forceBidder, multiRequest }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; const { pubdata, secid, pubid, source, content: bidParamsContent } = bid.params; - bidsMap[bidId] = bid; const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; if (jwTargeting) { @@ -200,8 +229,9 @@ export const spec = { requests.push(request); sources.push(source); - bidsArray.push(bid); + bidsArray.push(bidObject); } else { + bidsMap[bidId] = bidObject; imp.push(impObj); } } @@ -258,9 +288,11 @@ export const spec = { return; } + if (request.user) user = request.user; + const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); if (ortb2UserData && ortb2UserData.length) { - user = request.user || { data: [] }; + if (!user) user = { data: [] }; user = mergeDeep(user, { data: [...ortb2UserData] }); @@ -376,6 +408,11 @@ export const spec = { } }); + const criteoRequest = criteoBidRequests.length && criteoSpec.buildRequests(criteoBidRequests, bidderRequest); + if (criteoRequest) { + criteoRequest.criteoBidsMap = criteoBidsMap; + } + return [...requests.map((req, i) => { let sp; const url = (endpoint || ENDPOINT_URL).replace(/[?&]sp=([^?&=]+)/, (i, found) => { @@ -390,14 +427,14 @@ export const spec = { method: 'POST', url: urlWithParams, data: JSON.stringify(req), - bid: bidsArray[i], + bidObject: bidsArray[i], }; }), ...(mainRequest ? [{ method: 'POST', url: endpoint || ENDPOINT_URL, data: JSON.stringify(mainRequest), bidsMap - }] : [])]; + }] : []), ...(criteoRequest ? [criteoRequest] : [])]; }, /** * Unpack the response from the server into a list of bids. @@ -408,25 +445,33 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; + if (bidRequest.criteoBidsMap && bidRequest.bidRequests) { + const criteoBids = criteoSpec.interpretResponse(serverResponse, bidRequest); + return criteoBids.filter((bid) => { + const { savedPrebidBid } = bidRequest.criteoBidsMap[bid.requestId] || {}; + return canPublishResponse(bid.cpm, savedPrebidBid && savedPrebidBid.cpm); + }); + } else { + serverResponse = serverResponse && serverResponse.body; + const bidResponses = []; - let errorMessage; + let errorMessage; - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } + if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; + else if (serverResponse.seatbid && !serverResponse.seatbid.length) { + errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; + } - const bidderCode = this.forceBidderName || this.code; + const bidderCode = this.forceBidderName || this.code; - if (!errorMessage && serverResponse.seatbid) { - serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst, bidderCode); - }); + if (!errorMessage && serverResponse.seatbid) { + serverResponse.seatbid.forEach(respItem => { + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst, bidderCode); + }); + } + if (errorMessage) logError(errorMessage); + return bidResponses; } - if (errorMessage) logError(errorMessage); - return bidResponses; }, getUserSyncs: function (...args) { const [syncOptions,, gdprConsent, uspConsent] = args; @@ -501,8 +546,9 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); if (!errorMessage && !serverBid.adm && !serverBid.nurl) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); else { - const bid = bidRequest.bidsMap ? bidRequest.bidsMap[serverBid.impid] : bidRequest.bid; - if (bid) { + const bidObject = bidRequest.bidsMap ? bidRequest.bidsMap[serverBid.impid] : bidRequest.bidObject; + const { bid, savedPrebidBid } = bidObject || {}; + if (bid && canPublishResponse(serverBid.price, savedPrebidBid && savedPrebidBid.cpm)) { const bidResponse = { requestId: bid.bidId, // bid.bidderRequestId cpm: serverBid.price, @@ -518,6 +564,8 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid dealId: serverBid.dealid }; + bidObject.savedPrebidBid = bidResponse; + if (serverBid.ext && serverBid.ext.bidder && serverBid.ext.bidder.grid && serverBid.ext.bidder.grid.demandSource) { bidResponse.adserverTargeting = { 'hb_ds': serverBid.ext.bidder.grid.demandSource }; bidResponse.meta.demandSource = serverBid.ext.bidder.grid.demandSource; @@ -605,7 +653,7 @@ function getUserIdFromFPDStorage() { function segmentProcessing(segment, forceSegName) { return segment .map((seg) => { - const value = seg && (seg.value || seg.id || seg); + const value = seg && (seg.id || seg); if (typeof value === 'string' || typeof value === 'number') { return { value: value.toString(), @@ -663,6 +711,13 @@ function reformatKeywords(pageKeywords) { return Object.keys(formatedPageKeywords).length && formatedPageKeywords; } +function canPublishResponse(price, savedPrice) { + if (isNumber(savedPrice)) { + return price > savedPrice || (price === savedPrice && Math.random() > 0.5); + } + return true; +} + function outstreamRender (bid) { bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ @@ -696,4 +751,369 @@ export function getSyncUrl() { return SYNC_URL; } +// ================ Criteo methods ================== + +const criteoSpec = { + /** f + * @param {object} bid + * @return {boolean} + */ + isBidRequestValid: (bid) => { + // either one of zoneId or networkId should be set + if (!(bid && bid.params && (bid.params.zoneId || bid.params.networkId))) { + return false; + } + + // video media types requires some mandatory params + if (hasVideoMediaType(bid)) { + if (!hasValidVideoMediaType(bid)) { + return false; + } + } + + return true; + }, + + /** + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests: (bidRequests, bidderRequest) => { + let url; + let data; + let fpd = bidderRequest.ortb2 || {}; + + Object.assign(bidderRequest, { + publisherExt: fpd.site?.ext, + userExt: fpd.user?.ext, + ceh: config.getConfig('criteo.ceh'), + coppa: config.getConfig('coppa') + }); + + const context = buildContext(bidRequests, bidderRequest); + url = buildCdbUrl(context); + data = buildCdbRequest(context, bidRequests, bidderRequest); + + if (data) { + return { method: 'POST', url, data, bidRequests }; + } + }, + + /** + * @param {*} response + * @param {ServerRequest} request + * @return {Bid[]} + */ + interpretResponse: (response, request) => { + const body = response.body || response; + const bids = []; + + if (body && body.slots && isArray(body.slots)) { + body.slots.forEach(slot => { + const bidRequest = find(request.bidRequests, b => b.adUnitCode === slot.impid && (!b.params.zoneId || parseInt(b.params.zoneId) === slot.zoneid)); + const bidId = bidRequest.bidId; + const bid = { + requestId: bidId, + cpm: slot.cpm, + currency: slot.currency, + netRevenue: true, + ttl: slot.ttl || 60, + creativeId: slot.creativecode, + width: slot.width, + height: slot.height, + dealId: slot.dealCode, + }; + if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { + const pafResponseMeta = { + content_id: slot.ext.paf.content_id, + transmission: response.ext.paf.transmission + }; + bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); + } + if (slot.adomain) { + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain }); + } + if (slot.video) { + bid.vastUrl = slot.displayurl; + bid.mediaType = VIDEO; + } else { + bid.ad = slot.creative; + } + bids.push(bid); + }); + } + + return bids; + } +}; + +function readFromAllStorages(name) { + const fromCookie = storage.getCookie(name); + const fromLocalStorage = storage.getDataFromLocalStorage(name); + + return fromCookie || fromLocalStorage || undefined; +} + +/** + * @param {BidRequest[]} bidRequests + * @param bidderRequest + */ +function buildContext(bidRequests, bidderRequest) { + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + referrer = bidderRequest.refererInfo.page; + } + const queryString = parseUrl(bidderRequest?.refererInfo?.topmostLocation).search; + + const context = { + url: referrer, + debug: queryString['pbt_debug'] === '1', + noLog: queryString['pbt_nolog'] === '1', + amp: false, + }; + + bidRequests.forEach(bidRequest => { + if (bidRequest.params.integrationMode === 'amp') { + context.amp = true; + } + }); + + return context; +} + +/** + * @param {CriteoContext} context + * @return {string} + */ +function buildCdbUrl(context) { + let url = CDB_ENDPOINT; + url += '?profileId=' + PROFILE_ID_INLINE; + url += '&av=' + String(ADAPTER_VERSION_FOR_CRITEO_MODE); + url += '&wv=' + encodeURIComponent('$prebid.version$'); + url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); + + if (storage.localStorageIsEnabled()) { + url += '&lsavail=1'; + } else { + url += '&lsavail=0'; + } + + if (context.amp) { + url += '&im=1'; + } + if (context.debug) { + url += '&debug=1'; + } + if (context.noLog) { + url += '&nolog=1'; + } + + const bundle = readFromAllStorages(BUNDLE_COOKIE_NAME); + if (bundle) { + url += `&bundle=${bundle}`; + } + + const optout = readFromAllStorages(OPTOUT_COOKIE_NAME); + if (optout) { + url += `&optout=1`; + } + + const sid = readFromAllStorages(SID_COOKIE_NAME); + if (sid) { + url += `&sid=${sid}`; + } + + const idcpy = readFromAllStorages(IDCPY_COOKIE_NAME); + if (idcpy) { + url += `&idcpy=${idcpy}`; + } + + return url; +} + +/** + * @param {CriteoContext} context + * @param {BidRequest[]} bidRequests + * @param bidderRequest + * @return {*} + */ +function buildCdbRequest(context, bidRequests, bidderRequest) { + let networkId; + let schain; + const request = { + publisher: { + url: context.url, + ext: bidderRequest.publisherExt, + }, + regs: { + coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) + }, + slots: bidRequests.map(bidRequest => { + networkId = bidRequest.params.networkId || networkId; + schain = bidRequest.schain || schain; + const slot = { + impid: bidRequest.adUnitCode, + transactionid: bidRequest.transactionId, + auctionId: bidRequest.auctionId, + }; + if (bidRequest.params.zoneId) { + slot.zoneid = bidRequest.params.zoneId; + } + if (deepAccess(bidRequest, 'ortb2Imp.ext')) { + slot.ext = bidRequest.ortb2Imp.ext; + } + if (bidRequest.params.ext) { + slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); + } + if (bidRequest.params.publisherSubId) { + slot.publishersubid = bidRequest.params.publisherSubId; + } + + if (hasBannerMediaType(bidRequest)) { + slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseSize); + } else { + slot.sizes = []; + } + + if (hasVideoMediaType(bidRequest)) { + const video = { + playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), + mimes: bidRequest.mediaTypes.video.mimes, + protocols: bidRequest.mediaTypes.video.protocols, + maxduration: bidRequest.mediaTypes.video.maxduration, + api: bidRequest.mediaTypes.video.api, + skip: bidRequest.mediaTypes.video.skip, + placement: bidRequest.mediaTypes.video.placement, + minduration: bidRequest.mediaTypes.video.minduration, + playbackmethod: bidRequest.mediaTypes.video.playbackmethod, + startdelay: bidRequest.mediaTypes.video.startdelay + }; + const paramsVideo = bidRequest.params.video; + if (paramsVideo !== undefined) { + video.skip = video.skip || paramsVideo.skip || 0; + video.placement = video.placement || paramsVideo.placement; + video.minduration = video.minduration || paramsVideo.minduration; + video.playbackmethod = video.playbackmethod || paramsVideo.playbackmethod; + video.startdelay = video.startdelay || paramsVideo.startdelay || 0; + } + + slot.video = video; + } + + enrichSlotWithFloors(slot, bidRequest); + + return slot; + }), + }; + if (networkId) { + request.publisher.networkid = networkId; + } + if (schain) { + request.source = { + ext: { + schain: schain + } + }; + } + request.user = { + ext: bidderRequest.userExt + }; + if (bidderRequest && bidderRequest.ceh) { + request.user.ceh = bidderRequest.ceh; + } + if (bidderRequest && bidderRequest.gdprConsent) { + request.gdprConsent = {}; + if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { + request.gdprConsent.gdprApplies = !!(bidderRequest.gdprConsent.gdprApplies); + } + request.gdprConsent.version = bidderRequest.gdprConsent.apiVersion; + if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { + request.gdprConsent.consentData = bidderRequest.gdprConsent.consentString; + } + } + if (bidderRequest && bidderRequest.uspConsent) { + request.user.uspIab = bidderRequest.uspConsent; + } + return request; +} + +function parseSizes(sizes, parser = s => s) { + if (sizes === undefined) { + return []; + } + if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) + return sizes.map(size => parser(size)); + } + return [parser(sizes)]; // or a single one ? (ie. [728,90]) +} + +function parseSize(size) { + return size[0] + 'x' + size[1]; +} + +function hasVideoMediaType(bidRequest) { + return deepAccess(bidRequest, 'mediaTypes.video') !== undefined; +} + +function hasBannerMediaType(bidRequest) { + return deepAccess(bidRequest, 'mediaTypes.banner') !== undefined; +} + +function hasValidVideoMediaType(bidRequest) { + let isValid = true; + + var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod']; + + requiredMediaTypesParams.forEach(function (param) { + if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { + isValid = false; + logError('TheMediaGrid Bid Adapter (withCriteo mode): mediaTypes.video.' + param + ' is required'); + } + }); + + if (isValid) { + const videoPlacement = bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement; + // We do not support long form for now, also we have to check that context & placement are consistent + if (bidRequest.mediaTypes.video.context === 'instream' && videoPlacement === 1) { + return true; + } else if (bidRequest.mediaTypes.video.context === 'outstream' && videoPlacement !== 1) { + return true; + } + } + + return false; +} + +function enrichSlotWithFloors(slot, bidRequest) { + try { + const slotFloors = {}; + + if (bidRequest.getFloor) { + if (bidRequest.mediaTypes?.banner) { + slotFloors.banner = {}; + const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) + bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = bidRequest.getFloor({ size: bannerSize, mediaType: BANNER })); + } + + if (bidRequest.mediaTypes?.video) { + slotFloors.video = {}; + const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) + videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = bidRequest.getFloor({ size: videoSize, mediaType: VIDEO })); + } + + if (Object.keys(slotFloors).length > 0) { + if (!slot.ext) { + slot.ext = {} + } + Object.assign(slot.ext, { + floors: slotFloors + }); + } + } + } catch (e) { + logError('Could not parse floors from Prebid: ' + e); + } +} + registerBidder(spec); diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index bfe5f3952bc..c49b7619c07 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -34,8 +34,6 @@ const LOG_ERROR_MESS = { hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' }; -const VIDEO_KEYS = ['mimes', 'protocols', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [ VIDEO ], @@ -105,7 +103,7 @@ export const spec = { const impObj = { id: bidId.toString(), tagid: secid.toString(), - video: createVideoForImp(video, sizes, mediaTypes && mediaTypes.video), + video: createVideoForImp(mergeDeep({}, video, mediaTypes && mediaTypes.video), sizes), ext: { divid: adUnitCode.toString() } @@ -386,13 +384,7 @@ function createRenderer (bid, rendererParams) { return renderer; } -function createVideoForImp({ mind, maxd, size, ...paramsVideo }, bidSizes, bidVideo = {}) { - VIDEO_KEYS.forEach((key) => { - if (!(key in paramsVideo) && key in bidVideo) { - paramsVideo[key] = bidVideo[key]; - } - }); - +function createVideoForImp({ mind, maxd, size, ...paramsVideo }, bidSizes) { if (size && isStr(size)) { const sizeArray = size.split('x'); if (sizeArray.length === 2 && parseInt(sizeArray[0]) && parseInt(sizeArray[1])) { @@ -402,7 +394,7 @@ function createVideoForImp({ mind, maxd, size, ...paramsVideo }, bidSizes, bidVi } if (!paramsVideo.w || !paramsVideo.h) { - const playerSizes = bidVideo.playerSize && bidVideo.playerSize.length === 2 ? bidVideo.playerSize : bidSizes; + const playerSizes = paramsVideo.playerSize && paramsVideo.playerSize.length === 2 ? paramsVideo.playerSize : bidSizes; if (playerSizes) { const playerSize = playerSizes[0]; if (playerSize) { @@ -411,9 +403,13 @@ function createVideoForImp({ mind, maxd, size, ...paramsVideo }, bidSizes, bidVi } } - const durationRangeSec = bidVideo.durationRangeSec || []; - const minDur = mind || durationRangeSec[0] || bidVideo.minduration; - const maxDur = maxd || durationRangeSec[1] || bidVideo.maxduration; + if (paramsVideo.playerSize) { + delete paramsVideo.playerSize; + } + + const durationRangeSec = paramsVideo.durationRangeSec || []; + const minDur = mind || durationRangeSec[0] || paramsVideo.minduration; + const maxDur = maxd || durationRangeSec[1] || paramsVideo.maxduration; if (minDur) { paramsVideo.minduration = minDur; @@ -436,7 +432,7 @@ export function getSyncUrl() { function segmentProcessing(segment, forceSegName) { return segment .map((seg) => { - const value = seg && (seg.value || seg.id || seg); + const value = seg && (seg.id || seg); if (typeof value === 'string' || typeof value === 'number') { return { value: value.toString(), diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js new file mode 100644 index 00000000000..1f11b891139 --- /dev/null +++ b/modules/growthCodeAnalyticsAdapter.js @@ -0,0 +1,176 @@ +/** + * growthCodeAnalyticsAdapter.js - GrowthCode Analytics Adapter + */ +import { ajax } from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; +import CONSTANTS from '../src/constants.json'; +import { getStorageManager } from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {logError, logInfo} from '../src/utils.js'; + +const MODULE_NAME = 'growthCodeAnalytics'; +const DEFAULT_PID = 'INVALID_PID' +const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb/analytics' + +export const storage = getStorageManager(); + +let sessionId = utils.generateUUID(); + +let trackEvents = []; +let pid = DEFAULT_PID; +let url = ENDPOINT_URL; + +let eventQueue = []; + +let startAuction = 0; +let bidRequestTimeout = 0; +let analyticsType = 'endpoint'; + +let growthCodeAnalyticsAdapter = Object.assign(adapter({url: url, analyticsType}), { + track({eventType, eventData}) { + eventData = eventData ? JSON.parse(JSON.stringify(eventData)) : {}; + let data = {}; + if (!trackEvents.includes(eventType)) return; + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: { + data = eventData; + startAuction = data.timestamp; + bidRequestTimeout = data.timeout; + break; + } + + case CONSTANTS.EVENTS.AUCTION_END: { + data = eventData; + data.start = startAuction; + data.end = Date.now(); + break; + } + + case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + data.bidders = eventData; + break; + } + + case CONSTANTS.EVENTS.BID_TIMEOUT: { + data.bidders = eventData; + data.duration = bidRequestTimeout; + break; + } + + case CONSTANTS.EVENTS.BID_REQUESTED: { + data = eventData; + break; + } + + case CONSTANTS.EVENTS.BID_RESPONSE: { + data = eventData; + delete data.ad; + break; + } + + case CONSTANTS.EVENTS.BID_WON: { + data = eventData; + delete data.ad; + delete data.adUrl; + break; + } + + case CONSTANTS.EVENTS.BIDDER_DONE: { + data = eventData; + break; + } + + case CONSTANTS.EVENTS.SET_TARGETING: { + data.targetings = eventData; + break; + } + + case CONSTANTS.EVENTS.REQUEST_BIDS: { + data = eventData; + break; + } + + case CONSTANTS.EVENTS.ADD_AD_UNITS: { + data = eventData; + break; + } + + default: + return; + } + + data.eventType = eventType; + data.timestamp = data.timestamp || Date.now(); + + sendEvent(data); + } +}); + +growthCodeAnalyticsAdapter.originEnableAnalytics = growthCodeAnalyticsAdapter.enableAnalytics; + +growthCodeAnalyticsAdapter.enableAnalytics = function(conf = {}) { + if (typeof conf.options === 'object') { + if (conf.options.pid) { + pid = conf.options.pid; + url = conf.options.url ? conf.options.url : ENDPOINT_URL; + } else { + logError(MODULE_NAME + ' Not a valid PartnerID') + return + } + if (conf.options.trackEvents) { + trackEvents = conf.options.trackEvents; + } + } else { + logError(MODULE_NAME + ' Invalid configuration'); + return; + } + + growthCodeAnalyticsAdapter.originEnableAnalytics(conf); +}; + +function logToServer() { + if (pid === DEFAULT_PID) return; + if (eventQueue.length > 1) { + let data = { + session: sessionId, + pid: pid, + timestamp: Date.now(), + timezoneoffset: new Date().getTimezoneOffset(), + url: getRefererInfo().page, + referer: document.referrer, + events: eventQueue + }; + + ajax(url, { + success: response => { + logInfo(MODULE_NAME + ' Send Data to Server') + }, + error: error => { + logInfo(MODULE_NAME + ' Problem Send Data to Server: ' + error) + } + }, JSON.stringify(data), {method: 'POST', withCredentials: true}) + + eventQueue = [ + ]; + } +} + +function sendEvent(event) { + eventQueue.push(event); + logInfo(MODULE_NAME + 'Analytics Event: ' + event); + + if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { + logToServer(); + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: growthCodeAnalyticsAdapter, + code: 'growthCodeAnalytics' +}); + +growthCodeAnalyticsAdapter.logToServer = logToServer; + +export default growthCodeAnalyticsAdapter; diff --git a/modules/growthCodeAnalyticsAdapter.md b/modules/growthCodeAnalyticsAdapter.md new file mode 100644 index 00000000000..e45cb2e9c62 --- /dev/null +++ b/modules/growthCodeAnalyticsAdapter.md @@ -0,0 +1,41 @@ +## GrowthCode Analytics Adapter + +[GrowthCode](https://growthcode.io) offers scaled infrastructure-as-a-service to +empower independent publishers to harness data and take control of identity and +audience while rapidly aligning to industry changes and margin pressure. + +## Building Prebid with GrowthCode Support + +First, make sure to add the GrowthCode submodule to your Prebid.js package with: + +``` +gulp build --modules=growthCodeIdSystem,growthCodeAnalyticsAdapter,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.enableAnalytics({ + provider: 'growthCodeAnalytics', + options: { + pid: '', + trackEvents: [ + 'auctionEnd', + 'bidAdjustment', + 'bidTimeout', + 'bidRequested', + 'bidResponse', + 'noBid', + 'bidWon', + 'bidderDone'] + } +}); +``` + +| Param enableAnalytics | Scope | Type | Description | Example | +|-----------------------|----------|--------|-------------------------------------------------------------|--------------------------| +| provider | Required | String | The name of this Adapter. | `"growthCodeAnalytics"` | +| params | Required | Object | Details of module params. | | +| params.pid | Required | String | This is the Customer ID value obtained via Intimate Merger. | `""` | +| params.url | Optional | String | Custom URL for server | | +| params.trackEvents | Required | String | Name if the variable that holds your publisher ID | | diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index 68fe2c7e925..edd7cd33012 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -10,7 +10,7 @@ import {ajax} from '../src/ajax.js'; import { submodule } from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js'; -const GCID_EXPIRY = 45; +const GCID_EXPIRY = 7; const MODULE_NAME = 'growthCodeId'; const GC_DATA_KEY = '_gc_data'; const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb?' @@ -24,11 +24,15 @@ export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_ */ export function readData(key) { try { + let payload + if (storage.cookiesAreEnabled()) { + payload = tryParse(storage.getCookie(key)) + } if (storage.hasLocalStorage()) { - return storage.getDataFromLocalStorage(key); + payload = tryParse(storage.getDataFromLocalStorage(key)) } - if (storage.cookiesAreEnabled()) { - return storage.getCookie(key); + if ((payload.expire_at !== undefined) && (payload.expire_at > (Date.now() / 1000))) { + return payload } } catch (error) { logError(error); @@ -122,7 +126,7 @@ export const growthCodeIdSubmodule = { } const resp = function(callback) { - let gcData = tryParse(readData(GC_DATA_KEY)); + let gcData = readData(GC_DATA_KEY); if (gcData) { callback(gcData); } else { diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index fe15602c0c0..93306984ab1 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -294,6 +294,7 @@ function buildRequests(validBidRequests, bidderRequest) { const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const uspConsent = bidderRequest && bidderRequest.uspConsent; const timeout = config.getConfig('bidderTimeout'); + const coppa = config.getConfig('coppa') === true ? 1 : 0; const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page; _each(validBidRequests, bidRequest => { const { @@ -386,6 +387,9 @@ function buildRequests(validBidRequests, bidderRequest) { if (uspConsent) { data.uspConsent = uspConsent; } + if (coppa) { + data.coppa = coppa; + } if (schain && schain.nodes) { data.schain = _serializeSupplyChainObj(schain); } diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 2f10245cd59..a75c03ee1c4 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -8,8 +8,9 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; -import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; +import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; @@ -18,8 +19,9 @@ export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'} /** * Param or default. - * @param {String} param + * @param {String|function} param * @param {String} defaultVal + * @param arg */ function paramOrDefault(param, defaultVal, arg) { if (isFn(param)) { @@ -53,11 +55,11 @@ export const hadronIdSubmodule = { * @returns {Object} */ decode(value) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); + const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); if (isStr(hadronId)) { return {hadronId: hadronId}; } - return (value && typeof value['hadronId'] === 'string') ? { 'hadronId': value['hadronId'] } : undefined; + return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -70,37 +72,40 @@ export const hadronIdSubmodule = { config.params = {}; } const partnerId = config.params.partnerId | 0; - - const url = urlAddParams( - paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid` - ); - + let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); + if (isStr(hadronId)) { + return {id: {hadronId}}; + } const resp = function (callback) { - let hadronId = storage.getDataFromLocalStorage('auHadronId'); - if (isStr(hadronId)) { - const responseObj = {hadronId: hadronId}; - callback(responseObj); - } else { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - logError(error); - } + let responseObj = {}; + const callbacks = { + success: response => { + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); } - callback(responseObj); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); + logInfo(`Response from backend is ${responseObj}`); + hadronId = responseObj['hadronId']; + storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); + responseObj = {id: {hadronId}}; } - }; - ajax(url, callbacks, undefined, {method: 'GET'}); - } + callback(responseObj); + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + logInfo('HadronId not found in storage, calling backend...'); + const url = urlAddParams( + // config.params.url and config.params.urlArg are not documented + // since their use is for debugging purposes only + paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), + `partner_id=${partnerId}&_it=prebid` + ); + ajax(url, callbacks, undefined, {method: 'GET'}); }; return {callback: resp}; } diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js new file mode 100644 index 00000000000..92b0a1c18db --- /dev/null +++ b/modules/holidBidAdapter.js @@ -0,0 +1,175 @@ +import { + deepAccess, + getBidIdParameter, + isStr, + logMessage, + triggerPixel, +} from '../src/utils.js' +import * as events from '../src/events.js' +import CONSTANTS from '../src/constants.json' +import { BANNER } from '../src/mediaTypes.js' + +import { registerBidder } from '../src/adapters/bidderFactory.js' + +const BIDDER_CODE = 'holid' +const GVLID = 1177 +const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction' +const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html' +const TIME_TO_LIVE = 300 +let wurlMap = {} + +events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler) + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.adUnitID + }, + + buildRequests: function (validBidRequests, _bidderRequest) { + return validBidRequests.map((bid) => { + const requestData = { + ...bid.ortb2, + id: bid.auctionId, + imp: [getImp(bid)], + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(requestData), + bidId: bid.bidId, + } + }) + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = [] + + if (!serverResponse.body.seatbid) { + return [] + } + + serverResponse.body.seatbid.map((response) => { + response.bid.map((bid) => { + const requestId = bidRequest.bidId + const auctionId = bidRequest.auctionId + const wurl = deepAccess(bid, 'ext.prebid.events.win') + const bidResponse = { + requestId, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + creativeId: bid.crid, + currency: serverResponse.body.cur, + netRevenue: true, + ttl: TIME_TO_LIVE, + } + + addWurl({ auctionId, requestId, wurl }) + + bidResponses.push(bidResponse) + }) + }) + + return bidResponses + }, + + getUserSyncs(optionsType, serverResponse, gdprConsent, uspConsent) { + if (!serverResponse || serverResponse.length === 0) { + return [] + } + + const syncs = [] + const bidders = getBidders(serverResponse) + + if (optionsType.iframeEnabled && bidders) { + const queryParams = [] + + queryParams.push('bidders=' + bidders) + queryParams.push('gdpr=' + +gdprConsent.gdprApplies) + queryParams.push('gdpr_consent=' + gdprConsent.consentString) + queryParams.push('usp_consent=' + (uspConsent || '')) + + let strQueryParams = queryParams.join('&') + + if (strQueryParams.length > 0) { + strQueryParams = '?' + strQueryParams + } + + syncs.push({ + type: 'iframe', + url: COOKIE_SYNC_ENDPOINT + strQueryParams + '&type=iframe', + }) + + return syncs + } + + return [] + }, +} + +function getImp(bid) { + const imp = { + ext: { + prebid: { + storedrequest: { + id: getBidIdParameter('adUnitID', bid.params), + }, + }, + }, + } + const sizes = + bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes + + if (deepAccess(bid, 'mediaTypes.banner')) { + imp.banner = { + format: sizes.map((size) => { + return { w: size[0], h: size[1] } + }), + } + } + + return imp +} + +function getBidders(serverResponse) { + const bidders = serverResponse + .map((res) => Object.keys(res.body.ext.responsetimemillis || [])) + .flat(1) + + if (bidders.length) { + return encodeURIComponent(JSON.stringify([...new Set(bidders)])) + } +} + +function addWurl(auctionId, adId, wurl) { + if ([auctionId, adId].every(isStr)) { + wurlMap[`${auctionId}${adId}`] = wurl + } +} + +function removeWurl(auctionId, adId) { + delete wurlMap[`${auctionId}${adId}`] +} + +function getWurl(auctionId, adId) { + if ([auctionId, adId].every(isStr)) { + return wurlMap[`${auctionId}${adId}`] + } +} + +function bidWonHandler(bid) { + const wurl = getWurl(bid.auctionId, bid.adId) + if (wurl) { + logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) + triggerPixel(wurl) + removeWurl(bid.auctionId, bid.adId) + } +} + +registerBidder(spec) diff --git a/modules/holidBidAdapter.md b/modules/holidBidAdapter.md new file mode 100644 index 00000000000..1d83918c00a --- /dev/null +++ b/modules/holidBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Holid Bid Adapter +Module Type: Bidder Adapter +Maintainer: richard@holid.se +``` + +# Description + +Currently module supports only banner mediaType. + +# Test Parameters + +## Sample Banner Ad Unit + +```js +var adUnits = [ + { + code: 'bannerAdUnit', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: 'holid', + params: { + adUnitID: '12345', + }, + }, + ], + }, +] +``` diff --git a/modules/iasRtdProvider.js b/modules/iasRtdProvider.js index 605fff8898e..994be7c0804 100644 --- a/modules/iasRtdProvider.js +++ b/modules/iasRtdProvider.js @@ -38,7 +38,7 @@ const IAS_KEY_MAPPINGS = { /** * Module init - * @param {Object} provider + * @param {Object} config * @param {Object} userConsent * @return {boolean} */ @@ -71,12 +71,26 @@ function stringifySlotSizes(sizes) { return result; } -function stringifySlot(bidRequest) { +function getAdUnitPath(adSlot, bidRequest, adUnitPath) { + let p = bidRequest.code; + if (!utils.isEmpty(adSlot)) { + p = adSlot.gptSlot; + } else { + if (!utils.isEmpty(adUnitPath) && utils.hasOwn(adUnitPath, bidRequest.code)) { + if (utils.isStr(adUnitPath[bidRequest.code]) && !utils.isEmpty(adUnitPath[bidRequest.code])) { + p = adUnitPath[bidRequest.code]; + } + } + } + return p; +} + +function stringifySlot(bidRequest, adUnitPath) { const sizes = utils.getAdUnitSizes(bidRequest); const id = bidRequest.code; const ss = stringifySlotSizes(sizes); const adSlot = utils.getGptSlotInfoForAdUnitCode(bidRequest.code); - const p = utils.isEmpty(adSlot) ? bidRequest.code : adSlot.gptSlot; + const p = getAdUnitPath(adSlot, bidRequest, adUnitPath); const slot = { id, ss, p }; const keyValues = utils.getKeys(slot).map(function (key) { return [key, slot[key]].join(':'); @@ -119,12 +133,12 @@ function formatTargetingData(adUnit) { return renameKeyValues(result); } -function constructQueryString(anId, adUnits, pageUrl) { +function constructQueryString(anId, adUnits, pageUrl, adUnitPath) { let queries = []; queries.push(['anId', anId]); queries = queries.concat(adUnits.reduce(function (acc, request) { - acc.push(['slot', stringifySlot(request)]); + acc.push(['slot', stringifySlot(request, adUnitPath)]); return acc; }, [])); @@ -191,10 +205,11 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; const { pubId } = config.params; let { pageUrl } = config.params; + const { adUnitPath } = config.params; if (!isValidHttpUrl(pageUrl)) { pageUrl = document.location.href; } - const queryString = constructQueryString(pubId, adUnits, pageUrl); + const queryString = constructQueryString(pubId, adUnits, pageUrl, adUnitPath); ajax( `${IAS_HOST}?${queryString}`, getApiCallback(), diff --git a/modules/iasRtdProvider.md b/modules/iasRtdProvider.md index d8c46ff2697..be47822eb58 100644 --- a/modules/iasRtdProvider.md +++ b/modules/iasRtdProvider.md @@ -2,7 +2,7 @@ Module Name: Integral Ad Science(IAS) Rtd Provider Module Type: Rtd Provider -Maintainer: raguilar@integralads.com +Maintainer: punereporting@integralads.com # Description diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index c2c4a97c62e..a0745df37c8 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -247,7 +247,9 @@ class IdFetchFlow { 'gdpr': hasGdpr, 'nbPage': nbPage, 'o': 'pbjs', - 'rf': referer.topmostLocation, + 'tml': referer.topmostLocation, + 'ref': referer.ref, + 'cu': referer.canonicalUrl, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 0717cf43741..04f34bdc7d9 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -1,7 +1,7 @@ import { deepAccess, deepSetValue, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; -import {ajax} from '../src/ajax.js'; +import { ajax } from '../src/ajax.js'; import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'impactify'; @@ -27,6 +27,18 @@ const getDeviceType = () => { return 2; }; +const getFloor = (bid) => { + const floorInfo = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === DEFAULT_CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + return parseFloat(floorInfo.floor); + } + return null; +} + const createOpenRtbRequest = (validBidRequests, bidderRequest) => { // Create request and set imp bids inside let request = { @@ -114,6 +126,12 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { if (bid.params.container) { imp.ext.impactify.container = bid.params.container; } + if (typeof bid.getFloor === 'function') { + const floor = getFloor(bid); + if (floor) { + imp.bidfloor = floor; + } + } request.imp.push(imp); }); diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 044c27dc9e8..94f50094a91 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,74 +1,29 @@ -import { - cleanObj, - deepAccess, - deepClone, - deepSetValue, - getBidIdParameter, - getBidRequest, - getDNT, - getUniqueIdentifierStr, - isFn, - isPlainObject, - logWarn, - mergeDeep -} from '../src/utils.js'; +import {deepAccess, deepSetValue, getBidIdParameter, getUniqueIdentifierStr, logWarn, mergeDeep} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; -import {createEidsArray} from './userId/eids.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {loadExternalScript} from '../src/adloader.js'; const BIDDER_CODE = 'improvedigital'; const CREATIVE_TTL = 300; -const AD_SERVER_URL = 'https://ad.360yield.com/pb'; -const BASIC_ADS_URL = 'https://ad.360yield-basic.com/pb'; +const AD_SERVER_BASE_URL = 'https://ad.360yield.com'; +const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com'; +const PB_ENDPOINT = 'pb'; const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; const VIDEO_PARAMS = { DEFAULT_MIMES: ['video/mp4'], - SUPPORTED_PROPERTIES: ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', - 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', - 'api', 'companiontype', 'ext'], PLACEMENT_TYPE: { INSTREAM: 1, OUTSTREAM: 3, } }; -const NATIVE_DATA = { - VERSION: '1.2', - ASSET_TYPES: { - TITLE: 'title', - IMG: 'img', - DATA: 'data', - }, - ASSETS: { - title: {id: 0, name: 'title', assetType: 'title', default: {len: 140}}, - sponsoredBy: {id: 1, name: 'sponsoredBy', assetType: 'data', type: 1}, - icon: {id: 2, name: 'icon', assetType: 'img', type: 2}, - body: {id: 3, name: 'body', assetType: 'data', type: 2}, - image: {id: 4, name: 'image', assetType: 'img', type: 3}, - rating: {id: 5, name: 'rating', assetType: 'data', type: 3}, - likes: {id: 6, name: 'likes', assetType: 'data', type: 4}, - downloads: {id: 7, name: 'downloads', assetType: 'data', type: 5}, - price: {id: 8, name: 'price', assetType: 'data', type: 6}, - salePrice: {id: 9, name: 'salePrice', assetType: 'data', type: 7}, - phone: {id: 10, name: 'phone', assetType: 'data', type: 8}, - address: {id: 11, name: 'address', assetType: 'data', type: 9}, - body2: {id: 12, name: 'body2', assetType: 'data', type: 10}, - displayUrl: {id: 13, name: 'displayUrl', assetType: 'data', type: 11}, - cta: {id: 14, name: 'cta', assetType: 'data', type: 12}, - }, - getAssetById(id) { - return Object.values(this.ASSETS).find(asset => id === asset.id); - } -}; - export const spec = { code: BIDDER_CODE, gvlid: 253, @@ -94,85 +49,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests(bidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - - const request = { - cur: [config.getConfig('currency.adServerCurrency') || 'USD'], - ext: { - improvedigital: { - sdk: { - name: 'pbjs', - version: '$prebid.version$', - } - } - } - }; - - // Device - request.device = (typeof config.getConfig('device') === 'object') ? config.getConfig('device') : {}; - request.device.w = request.device.w || window.innerWidth; - request.device.h = request.device.h || window.innerHeight; - if (getDNT()) { - request.device.dnt = 1; - } - - // Coppa - const coppa = config.getConfig('coppa'); - if (typeof coppa === 'boolean') { - deepSetValue(request, 'regs.coppa', Number(coppa)); - } - - if (bidderRequest) { - // GDPR - const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - deepSetValue(request, 'regs.ext.gdpr', Number(gdprConsent.gdprApplies)); - } - deepSetValue(request, 'user.ext.consent', gdprConsent.consentString); - - // Additional Consent String - const additionalConsent = deepAccess(gdprConsent, 'addtlConsent'); - if (additionalConsent && additionalConsent.indexOf('~') !== -1) { - // Google Ad Tech Provider IDs - const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); - if (atpIds) { - deepSetValue( - request, - 'user.ext.consented_providers_settings.consented_providers', - atpIds.split('.').map(id => parseInt(id, 10)) - ); - } - } - } - - // Timeout - if (bidderRequest.timeout) { - request.tmax = parseInt(bidderRequest.timeout); - } - // US Privacy - if (typeof bidderRequest.uspConsent !== typeof undefined) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - } - - ID_REQUEST.buildSiteOrApp(request, bidderRequest); - - const bidRequest0 = bidRequests[0]; - - deepSetValue(request, 'source.ext.schain', bidRequest0.schain); - deepSetValue(request, 'source.tid', bidRequest0.transactionId); - // Save a placement id to send it to the ad server when fetching the user syncs - this.syncStore.placementId = this.syncStore.placementId || bidRequest0.params.placementId; - - if (bidRequest0.userId) { - const eids = createEidsArray(bidRequest0.userId); - deepSetValue(request, 'user.ext.eids', eids.length ? eids : undefined); - } - - return ID_REQUEST.buildServerRequests(request, bidRequests, bidderRequest); + this.syncStore.placementId = this.syncStore.placementId || bidRequests[0].params.placementId; + return ID_REQUEST.buildServerRequests(bidRequests, bidderRequest); }, /** @@ -182,48 +61,8 @@ export const spec = { * @param bidderRequest * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse(serverResponse, { bidderRequest }) { - if (!Array.isArray(deepAccess(serverResponse, 'body.seatbid'))) { - return []; - } - - const bids = []; - - serverResponse.body.seatbid.forEach(seatbid => { - if (!Array.isArray(seatbid.bid)) return; - - seatbid.bid.forEach(bidObject => { - if (!bidObject.adm || !bidObject.price || bidObject.hasOwnProperty('errorCode')) { - return; - } - const bidRequest = getBidRequest(bidObject.impid, [bidderRequest]); - const idExt = deepAccess(bidObject, `ext.${BIDDER_CODE}`, {}); - - const bid = { - requestId: bidObject.impid, - cpm: bidObject.price, - creativeId: bidObject.crid, - currency: serverResponse.body.cur.toUpperCase() || 'USD', - dealId: (typeof idExt.buying_type === 'string' && idExt.buying_type !== 'rtb') ? idExt.line_item_id : undefined, - meta: { - advertiserDomains: bidObject.adomain ? bidObject.adomain : [] - }, - netRevenue: idExt.is_net || false, - ttl: CREATIVE_TTL - } - - ID_RESPONSE.buildAd(bid, bidRequest, bidObject); - - ID_RAZR.forwardBid({ - bidRequest, - bid - }); - - bids.push(bid); - }); - }); - - return bids; + interpretResponse(serverResponse, { ortbRequest }) { + return CONVERTER.fromORTB({request: ortbRequest, response: serverResponse.body}).bids; }, /** @@ -241,6 +80,13 @@ export const spec = { const syncs = []; if ((this.syncStore.extendMode || !syncOptions.pixelEnabled) && syncOptions.iframeEnabled) { const { gdprApplies, consentString } = gdprConsent || {}; + const bidders = new Set(); + if (this.syncStore.extendMode && serverResponses) { + serverResponses.forEach(response => { + if (!response?.body?.ext?.responsetimemillis) return; + Object.keys(response.body.ext.responsetimemillis).forEach(b => bidders.add(b)) + }) + } syncs.push({ type: 'iframe', url: IFRAME_SYNC_URL + @@ -248,7 +94,8 @@ export const spec = { (this.syncStore.extendMode ? '&pbs=1' : '') + (typeof gdprApplies === 'boolean' ? `&gdpr=${Number(gdprApplies)}` : '') + (consentString ? `&gdpr_consent=${consentString}` : '') + - (uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '') + (uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '') + + (bidders.size ? `&bidders=${[...bidders].join(',')}` : '') }); } else if (syncOptions.pixelEnabled) { serverResponses.forEach(response => { @@ -267,38 +114,191 @@ export const spec = { registerBidder(spec); +export const CONVERTER = ortbConverter({ + context: { + ttl: CREATIVE_TTL, + nativeRequest: { + eventtrackers: [ + {event: 1, methods: [1, 2]}, + ] + } + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + imp.secure = Number(window.location.protocol === 'https:'); + if (!imp.bidfloor && bidRequest.params.bidFloor) { + imp.bidfloor = bidRequest.params.bidFloor; + imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' + } + const bidderParamsPath = context.extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; + const placementId = bidRequest.params.placementId; + if (placementId) { + deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); + if (context.extendMode) { + deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); + } + } else { + deepSetValue(imp, `${bidderParamsPath}.publisherId`, getBidIdParameter('publisherId', bidRequest.params)); + deepSetValue(imp, `${bidderParamsPath}.placementKey`, getBidIdParameter('placementKey', bidRequest.params)); + } + deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined); + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + mergeDeep(request, { + id: getUniqueIdentifierStr(), + source: { + // TODO: once https://github.com/prebid/Prebid.js/issues/8573 is resolved, this should be handled by the base ortbConverter logic + tid: context.bidRequests[0].transactionId, + }, + ext: { + improvedigital: { + sdk: { + name: 'pbjs', + version: '$prebid.version$', + } + } + }, + }); + return request; + }, + bidResponse(buildBidResponse, bid, context) { + if (!bid.adm || !bid.price || bid.hasOwnProperty('errorCode')) { + return; + } + const {bidRequest} = context; + context.mediaType = (() => { + const requestMediaTypes = Object.keys(bidRequest.mediaTypes); + if (requestMediaTypes.length === 1) return requestMediaTypes[0]; + // Detect media type for multi-format response + if (bid.adm.search(/^(<\?xml| parseInt(id, 10)) + ); + } + } + } + } + } +}) + const ID_REQUEST = { - buildServerRequests(basicRequest, bidRequests, bidderRequest) { + buildServerRequests(bidRequests, bidderRequest) { const globalExtendMode = config.getConfig('improvedigital.extend') === true; const requests = []; const singleRequestMode = config.getConfig('improvedigital.singleRequest') === true; - const extendImps = []; - const adServerImps = []; + const extendBids = []; + const adServerBids = []; - function formatRequest(imps, transactionId, extendMode) { - const request = deepClone(basicRequest); - request.imp = imps; - request.id = getUniqueIdentifierStr(); - if (transactionId) { - deepSetValue(request, 'source.tid', transactionId); + function adServerUrl(extendMode, publisherId) { + if (extendMode) { + return EXTEND_URL; } - const adServerUrl = hasPurpose1Consent(bidderRequest?.gdprConsent) ? AD_SERVER_URL : BASIC_ADS_URL; + const urlSegments = []; + urlSegments.push(hasPurpose1Consent(bidderRequest?.gdprConsent) ? AD_SERVER_BASE_URL : BASIC_ADS_BASE_URL) + if (publisherId) { + urlSegments.push(publisherId) + } + urlSegments.push(PB_ENDPOINT) + return urlSegments.join('/'); + } + + function formatRequest(bidRequests, publisherId, extendMode) { + const ortbRequest = CONVERTER.toORTB({bidRequests, bidderRequest, context: {extendMode}}); return { method: 'POST', - url: extendMode ? EXTEND_URL : adServerUrl, - data: JSON.stringify(request), + url: adServerUrl(extendMode, publisherId), + data: JSON.stringify(ortbRequest), + ortbRequest, bidderRequest } - }; + } + let publisherId = null; bidRequests.map((bidRequest) => { + const bidParamsPublisherId = bidRequest.params.publisherId; const extendModeEnabled = this.isExtendModeEnabled(globalExtendMode, bidRequest.params); - const imp = this.buildImp(bidRequest, extendModeEnabled); if (singleRequestMode) { - extendModeEnabled ? extendImps.push(imp) : adServerImps.push(imp); + if (!publisherId) { + publisherId = bidParamsPublisherId; + } else if (bidParamsPublisherId && publisherId !== bidParamsPublisherId) { + throw new Error(`All Improve Digital placements in a single call must have the same publisherId. Please check your 'params.publisherId' or turn off the single request mode.`); + } + extendModeEnabled ? extendBids.push(bidRequest) : adServerBids.push(bidRequest); } else { - requests.push(formatRequest([imp], bidRequest.transactionId, extendModeEnabled)); + requests.push(formatRequest([bidRequest], bidParamsPublisherId, extendModeEnabled)); } }); @@ -306,11 +306,11 @@ const ID_REQUEST = { return requests; } // In the single request mode, split imps between those going to the ad server and those going to extend server - if (extendImps.length) { - requests.push(formatRequest(extendImps, bidderRequest.auctionId, true)); + if (extendBids.length) { + requests.push(formatRequest(extendBids, publisherId, true)); } - if (adServerImps.length) { - requests.push(formatRequest(adServerImps, bidderRequest.auctionId, false)); + if (adServerBids.length) { + requests.push(formatRequest(adServerBids, publisherId, false)); } return requests; @@ -324,289 +324,10 @@ const ID_REQUEST = { return extendMode; }, - buildImp(bidRequest, extendMode) { - const imp = { - id: getBidIdParameter('bidId', bidRequest) || getUniqueIdentifierStr(), - secure: Number(window.location.protocol === 'https:'), - }; - - // Floor - const bidFloor = this.getBidFloor(bidRequest) || getBidIdParameter('bidFloor', bidRequest.params); - if (bidFloor) { - const bidFloorCur = getBidIdParameter('bidFloorCur', bidRequest.params) || 'USD'; - deepSetValue(imp, 'bidfloor', bidFloor); - deepSetValue(imp, 'bidfloorcur', bidFloorCur ? bidFloorCur.toUpperCase() : undefined); - } - - const bidderParamsPath = extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; - const placementId = getBidIdParameter('placementId', bidRequest.params); - if (placementId) { - deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); - if (extendMode) { - deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); - } - } else { - deepSetValue(imp, `${bidderParamsPath}.publisherId`, getBidIdParameter('publisherId', bidRequest.params)); - deepSetValue(imp, `${bidderParamsPath}.placementKey`, getBidIdParameter('placementKey', bidRequest.params)); - } - - deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined); - - // Adding GPID - const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || - deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot') || - deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.adslot'); - - deepSetValue(imp, 'ext.gpid', gpid); - - // Adding Interstitial Signal - if (deepAccess(bidRequest, 'ortb2Imp.instl')) { - imp.instl = 1; - } - - const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); - if (videoParams) { - imp.video = this.buildVideoRequest(bidRequest); - deepSetValue(imp, 'ext.is_rewarded_inventory', (videoParams.rewarded === 1 || deepAccess(videoParams, 'ext.rewarded') === 1) || undefined); - } - - if (deepAccess(bidRequest, 'mediaTypes.banner')) { - imp.banner = this.buildBannerRequest(bidRequest); - } - - if (deepAccess(bidRequest, 'mediaTypes.native')) { - const nativeImp = this.buildNativeRequest(bidRequest); - if (nativeImp) { - imp.native = nativeImp; - } - } - - return imp; - }, - - buildVideoRequest(bidRequest) { - const videoParams = deepClone(bidRequest.mediaTypes.video); - const videoImproveParams = deepClone(deepAccess(bidRequest, 'params.video', {})); - const video = {...videoParams, ...videoImproveParams}; - - if (Array.isArray(video.playerSize)) { - // Player size can be defined as [w, h] or [[w, h]] - const size = Array.isArray(video.playerSize[0]) ? video.playerSize[0] : video.playerSize; - video.w = size[0]; - video.h = size[1]; - } - video.placement = this.isOutstreamVideo(bidRequest) ? VIDEO_PARAMS.PLACEMENT_TYPE.OUTSTREAM : VIDEO_PARAMS.PLACEMENT_TYPE.INSTREAM; - - // Mimes is required - if (!video.mimes) { - video.mimes = VIDEO_PARAMS.DEFAULT_MIMES; - } - - // skip must be 0 or 1 - if (video.skip !== 1) { - delete video.skipmin; - delete video.skipafter; - if (video.skip !== 0) { - logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); - delete video.skip; - } - } - - Object.keys(video).forEach(prop => { - if (VIDEO_PARAMS.SUPPORTED_PROPERTIES.indexOf(prop) === -1) delete video[prop]; - }); - return video; - }, - - buildBannerRequest(bidRequest) { - // Set the desired creative sizes - // Input Format: array of pairs, i.e. [[300, 250], [250, 250]] - // Unless improvedigital.usePrebidSizes == true, no sizes are sent to the server - // and the sizes defined in the server for the placement will be used - const banner = {}; - if (config.getConfig('improvedigital.usePrebidSizes') === true && bidRequest.sizes) { - // Convert sizes from [x, y] to { w: x, h: y} - banner.format = bidRequest.sizes.map(sizePair => ({w: sizePair[0], h: sizePair[1]})); - } - return banner; - }, - - buildNativeRequest(bidRequest) { - const nativeParams = bidRequest.nativeParams; - if (!nativeParams) { - return null; - } - const request = { - eventtrackers: [ - {event: 1, methods: [1, 2]} - ], - assets: [], - } - for (let i of Object.keys(nativeParams)) { - const assetOrtbParams = NATIVE_DATA.ASSETS[i]; - if (assetOrtbParams) { - const assetParams = nativeParams[i]; - const asset = { - id: assetOrtbParams.id, - required: Number(assetParams.required), - }; - switch (assetOrtbParams.assetType) { - case NATIVE_DATA.ASSET_TYPES.TITLE: - asset.title = {len: assetParams.len || assetOrtbParams.default.len}; - break; - case NATIVE_DATA.ASSET_TYPES.DATA: - asset.data = cleanObj({type: assetOrtbParams.type, len: assetParams.len}) - break; - case NATIVE_DATA.ASSET_TYPES.IMG: - asset.img = cleanObj({ - type: assetOrtbParams.type, - w: deepAccess(assetParams, 'sizes.0'), - h: deepAccess(assetParams, 'sizes.1'), - wmin: deepAccess(assetParams, 'aspect_ratios.0.min_width'), - hmin: deepAccess(assetParams, 'aspect_ratios.0.min_height') - }); - break; - default: - return; - } - request.assets.push(asset); - } - } - if (!request.assets.length) { - logWarn('No native assets recognized. Ignoring native ad request'); - return null; - } - return { ver: NATIVE_DATA.VERSION, request: JSON.stringify(request) }; - }, - isOutstreamVideo(bidRequest) { return deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream'; }, - getBidFloor(bidRequest) { - if (!isFn(bidRequest.getFloor)) { - return null; - } - const floor = bidRequest.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; - }, - - buildSiteOrApp(request, bidderRequest) { - const app = {}; - const configAppSettings = config.getConfig('app') || {}; - const fpdAppSettings = bidderRequest.ortb2?.app || {}; - mergeDeep(app, configAppSettings, fpdAppSettings); - - if (Object.keys(app).length !== 0) { - request.app = app; - } else { - const site = {}; - const url = deepAccess(bidderRequest, 'refererInfo.page'); - if (url) { - site.page = url; - site.domain = bidderRequest.refererInfo.domain; - } - const configSiteSettings = config.getConfig('site') || {}; - const fpdSiteSettings = deepAccess(bidderRequest, 'ortb2.site') || {}; - mergeDeep(site, configSiteSettings, fpdSiteSettings); - request.site = site; - } - }, -}; - -const ID_RESPONSE = { - buildAd(bid, bidRequest, bidResponse) { - if (bidRequest.mediaTypes && Object.keys(bidRequest.mediaTypes).length === 1) { - if (deepAccess(bidRequest, 'mediaTypes.video')) { - this.buildVideoAd(bid, bidRequest, bidResponse); - } else if (deepAccess(bidRequest, 'mediaTypes.banner')) { - this.buildBannerAd(bid, bidRequest, bidResponse); - } else if (deepAccess(bidRequest, 'mediaTypes.native')) { - this.buildNativeAd(bid, bidRequest, bidResponse) - } - } else { - // Detect media type for multi-format response - if (bidResponse.adm.search(/^(<\?xml| { - // Only handle impression event. Viewability events are not supported yet. - if (tracker.event !== 1) return; - switch (tracker.method) { - case 1: // img - nativeAd.impressionTrackers.push(tracker.url); - break; - case 2: // js - // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. - nativeAd.javascriptTrackers = ``; - break; - } - }); - } else { - nativeAd.impressionTrackers = nativeResponse.imptrackers || []; - nativeAd.javascriptTrackers = nativeResponse.jstracker; - } - nativeResponse.assets.map(asset => { - const assetParams = NATIVE_DATA.getAssetById(asset.id); - switch (assetParams.assetType) { - case NATIVE_DATA.ASSET_TYPES.TITLE: - nativeAd.title = asset.title.text; - break; - case NATIVE_DATA.ASSET_TYPES.DATA: - nativeAd[assetParams.name] = asset.data.value; - break; - case NATIVE_DATA.ASSET_TYPES.IMG: - nativeAd[assetParams.name] = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h, - }; - break; - } - }); - bid.native = nativeAd; - }, }; const ID_OUTSTREAM = { diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js index 914ef0b904e..d054309ee40 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -68,6 +68,8 @@ export const spec = { requestId: response.slotBidId, cpm: response.cpm, currency: response.currency || DEFAULT_CURRENCY, + adType: response.adType || '1', + settings: response.settings, width: response.adWidth, height: response.adHeight, ttl: CREATIVE_TTL, diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 6913c98013c..360a373ba9a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -16,15 +16,15 @@ import { parseQueryStringParameters, safeJSONParse } from '../src/utils.js'; -import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; import CONSTANTS from '../src/constants.json'; -import {getStorageManager} from '../src/storageManager.js'; +import { getStorageManager } from '../src/storageManager.js'; import * as events from '../src/events.js'; -import {find} from '../src/polyfill.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import {Renderer} from '../src/Renderer.js'; +import { find } from '../src/polyfill.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'ix'; const ALIAS_BIDDER_CODE = 'roundel'; @@ -38,7 +38,6 @@ const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NATIVE_TIME_TO_LIVE = 3600; // Since native can have video, use ttl same as video const NET_REVENUE = true; -const MAX_REQUEST_SIZE = 8000; const MAX_REQUEST_LIMIT = 4; const OUTSTREAM_MINIMUM_PLAYER_SIZE = [144, 144]; const PRICE_TO_DOLLAR_FACTOR = { @@ -77,7 +76,8 @@ const SOURCE_RTI_MAPPING = { 'epsilon.com': '', // Publisher Link, publinkId 'audigent.com': '', // Hadron ID from Audigent, hadronId 'pubcid.org': '', // SharedID, pubcid - 'trustpid.com': '' // Trustpid + 'trustpid.com': '', // Trustpid + 'intimatemerger.com': '' }; const PROVIDERS = [ 'britepoolid', @@ -102,13 +102,13 @@ const VIDEO_PARAMS_ALLOW_LIST = [ const LOCAL_STORAGE_KEY = 'ixdiag'; export const LOCAL_STORAGE_FEATURE_TOGGLES_KEY = `${BIDDER_CODE}_features`; let hasRegisteredHandler = false; -export const storage = getStorageManager({gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({ gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { featureToggles: {}, - isFeatureEnabled: function(ft) { + isFeatureEnabled: function (ft) { return deepAccess(this.featureToggles, `features.${ft}.activated`) }, - getFeatureToggles: function() { + getFeatureToggles: function () { if (storage.localStorageIsEnabled()) { const parsedToggles = safeJSONParse(storage.getDataFromLocalStorage(LOCAL_STORAGE_FEATURE_TOGGLES_KEY)); if (deepAccess(parsedToggles, 'expiry') && parsedToggles.expiry >= new Date().getTime()) { @@ -118,7 +118,7 @@ export const FEATURE_TOGGLES = { } } }, - setFeatureToggles: function(serverResponse) { + setFeatureToggles: function (serverResponse) { const responseBody = serverResponse.body; const expiryTime = new Date(); const toggles = deepAccess(responseBody, 'ext.features'); @@ -133,7 +133,7 @@ export const FEATURE_TOGGLES = { } } }, - clearFeatureToggles: function() { + clearFeatureToggles: function () { this.featureToggles = {}; if (storage.localStorageIsEnabled()) { storage.removeDataFromLocalStorage(LOCAL_STORAGE_FEATURE_TOGGLES_KEY); @@ -152,6 +152,11 @@ const MEDIA_TYPES = { Native: 4 }; +let baseRequestSize = 0; +let currentRequestSize = 0; +let wasAdUnitImpressionsTrimmed = false; +let currentImpressionSize = 0; + /** * Transform valid bid request config object to banner impression object that will be sent to ad server. * @@ -159,7 +164,7 @@ const MEDIA_TYPES = { * @return {object} A impression object that will be sent to ad server. */ function bidToBannerImp(bid) { - const imp = bidToImp(bid); + const imp = bidToImp(bid, BANNER); imp.banner = {}; const impSize = deepAccess(bid, 'params.size'); if (impSize) { @@ -181,7 +186,7 @@ function bidToBannerImp(bid) { * @return {object} A impression object that will be sent to ad server. */ function bidToVideoImp(bid) { - const imp = bidToImp(bid); + const imp = bidToImp(bid, VIDEO); const videoAdUnitRef = deepAccess(bid, 'mediaTypes.video'); const videoParamRef = deepAccess(bid, 'params.video'); const videoParamErrors = checkVideoParams(videoAdUnitRef, videoParamRef); @@ -249,7 +254,7 @@ function bidToVideoImp(bid) { * @return {object} A impression object that will be sent to ad server. */ function bidToNativeImp(bid) { - const imp = bidToImp(bid); + const imp = bidToImp(bid, NATIVE); const request = bid.nativeOrtbRequest request.eventtrackers = [{ @@ -276,13 +281,28 @@ function bidToNativeImp(bid) { * @param {object} bid PBJS bid object * @returns {object} IX impression object */ -function bidToImp(bid) { +function bidToImp(bid, mediaType) { const imp = {}; imp.id = bid.bidId; imp.ext = {}; - imp.ext.siteID = bid.params.siteId.toString(); + + if (deepAccess(bid, `params.${mediaType}.siteId`) && !isNaN(Number(bid.params[mediaType].siteId))) { + switch (mediaType) { + case BANNER: + imp.ext.siteID = bid.params.banner.siteId.toString(); + break; + case VIDEO: + imp.ext.siteID = bid.params.video.siteId.toString(); + break; + case NATIVE: + imp.ext.siteID = bid.params.native.siteId.toString(); + break; + } + } else { + imp.ext.siteID = bid.params.siteId.toString(); + } // populate imp level sid if (bid.params.hasOwnProperty('id') && (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) { @@ -366,8 +386,8 @@ function parseBid(rawBid, currency, bidRequest) { bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; - - if (rawBid.mtype == MEDIA_TYPES.Video) { + // If mtype = video is passed and vastURl is not set, set vastxml + if (rawBid.mtype == MEDIA_TYPES.Video && ((rawBid.ext && !rawBid.ext.vasturl) || !rawBid.ext)) { bid.vastXml = rawBid.adm; } else if (rawBid.ext && rawBid.ext.vasturl) { bid.vastUrl = rawBid.ext.vasturl; @@ -391,7 +411,7 @@ function parseBid(rawBid, currency, bidRequest) { bid.mediaTypes = bidRequest.mediaTypes; bid.ttl = isValidExpiry ? rawBid.exp : VIDEO_TIME_TO_LIVE; } else if (parsedAdm && parsedAdm.native) { - bid.native = {ortb: parsedAdm.native}; + bid.native = { ortb: parsedAdm.native }; bid.width = rawBid.w ? rawBid.w : 1; bid.height = rawBid.h ? rawBid.h : 1; bid.mediaType = NATIVE; @@ -411,7 +431,6 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.adomain && rawBid.adomain.length > 0) { bid.meta.advertiserDomains = rawBid.adomain; } - return bid; } @@ -579,28 +598,23 @@ function getEidInfo(allEids) { * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { + baseRequestSize = 0; + currentRequestSize = 0; + wasAdUnitImpressionsTrimmed = false; + currentImpressionSize = 0; + // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; // Get ids from Prebid User ID Modules let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids')); let userEids = eidInfo.toSend; - const pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); + + let MAX_REQUEST_SIZE = 8000; // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { - let identityInfo = window.headertag.getIdentityInfo(); - if (identityInfo && typeof identityInfo === 'object') { - for (const partnerName in identityInfo) { - if (identityInfo.hasOwnProperty(partnerName)) { - let response = identityInfo[partnerName]; - if (!response.responsePending && response.data && typeof response.data === 'object' && - Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { - userEids.push(response.data); - } - } - } - } + addRTI(userEids, eidInfo); } // If `roundel` alias bidder, only send requests if liveramp ids exist. @@ -608,9 +622,123 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { return []; } - const r = {}; - const tmax = config.getConfig('bidderTimeout'); + const requests = []; + let r = createRequest(validBidRequests); + + // getting ixdiags for adunits of the video, outstream & multi format (MF) style + let ixdiag = buildIXDiag(validBidRequests); + for (var key in ixdiag) { + r.ext.ixdiag[key] = ixdiag[key]; + } + + r = enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids); + + r = applyRegulations(r, bidderRequest); + + let payload = {}; + createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE); + + let requestSequenceNumber = 0; + const transactionIds = Object.keys(impressions); + let isFpdAdded = false; + + for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { + // buildRequestV2 does not have request spliting logic. + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { + if (currentRequestSize >= MAX_REQUEST_SIZE) { + break; + } + } + if (requests.length >= MAX_REQUEST_LIMIT) { + break; + } + + r = addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE); + currentRequestSize += currentImpressionSize; + + const fpd = deepAccess(bidderRequest, 'ortb2') || {}; + const site = { ...(fpd.site || fpd.context) }; + const user = { ...fpd.user }; + if (!isEmpty(fpd) && !isFpdAdded) { + r = addFPD(bidderRequest, r, fpd, site, user); + + const clonedRObject = deepClone(r); + + clonedRObject.site = mergeDeep({}, clonedRObject.site, site); + clonedRObject.user = mergeDeep({}, clonedRObject.user, user); + + const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + + if (requestSize < MAX_REQUEST_SIZE) { + r.site = mergeDeep({}, r.site, site); + r.user = mergeDeep({}, r.user, user); + isFpdAdded = true; + const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; + currentRequestSize += fpdRequestSize; + } else { + logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); + } + } + // add identifiers info to ixDiag + r = addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE); + + const isLastAdUnit = adUnitIndex === transactionIds.length - 1; + + if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { + if (!isLastAdUnit || requestSequenceNumber) { + r.ext.ixdiag.sn = requestSequenceNumber; + } + + requestSequenceNumber++; + + requests.push({ + method: 'POST', + url: baseUrl + '?s=' + siteID, + data: deepClone(r), + option: { + contentType: 'text/plain', + }, + validBidRequests + }); + + currentRequestSize = baseRequestSize; + r.imp = []; + isFpdAdded = false; + } + } + + return requests; +} + +/** + * addRTI adds RTI info of the partner to retrieved user IDs from prebid ID module. + * + * @param {array} userEids userEids info retrieved from prebid + * @param {array} eidInfo eidInfo info from prebid + */ +function addRTI(userEids, eidInfo) { + let identityInfo = window.headertag.getIdentityInfo(); + if (identityInfo && typeof identityInfo === 'object') { + for (const partnerName in identityInfo) { + if (identityInfo.hasOwnProperty(partnerName)) { + let response = identityInfo[partnerName]; + if (!response.responsePending && response.data && typeof response.data === 'object' && + Object.keys(response.data).length && !eidInfo.seenSources[response.data.source]) { + userEids.push(response.data); + } + } + } + } +} + +/** + * createRequest creates the base request object + * @param {array} validBidRequests A list of valid bid request config objects. + * @return {object} Object describing the request to the server. + */ +function createRequest(validBidRequests) { + const r = {}; // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId.toString(); r.site = {}; @@ -620,13 +748,21 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag.ls = storage.localStorageIsEnabled(); r.imp = []; r.at = 1; + return r +} - // getting ixdiags for adunits of the video, outstream & multi format (MF) style - let ixdiag = buildIXDiag(validBidRequests); - for (var key in ixdiag) { - r.ext.ixdiag[key] = ixdiag[key]; - } - +/** + * enrichRequest adds userSync configs, source, and referer info to request and ixDiag objects. + * + * @param {object} r Base reuqest object. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {array} impressions A list of impressions to be added to the request. + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {array} userEids User ID info retrieved from Prebid ID module. + * @return {object} Enriched object describing the request to the server. + */ +function enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids) { + const tmax = deepAccess(bidderRequest, 'timeout'); if (tmax) { r.ext.ixdiag.tmax = tmax; } @@ -642,6 +778,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag.err = cachedErrors; } + // Add number of available imps to ixDiag. + r.ext.ixdiag.imps = Object.keys(impressions).length; + // set source.tid to auctionId for outgoing request to Exchange. r.source = { tid: validBidRequests[0].auctionId, @@ -662,6 +801,17 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.site.ref = document.referrer; } + return r +} + +/** + * applyRegulations applies regulation info such as GDPR and GPP to the reqeust obejct. + * + * @param {object} r Base reuqest object. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @return {object} Object enriched with regulation info describing the request to the server. + */ +function applyRegulations(r, bidderRequest) { // Apply GDPR information to the request if GDPR is enabled. if (bidderRequest) { if (bidderRequest.gdprConsent) { @@ -694,48 +844,53 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { usPrivacy = bidderRequest.uspConsent; } + const pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); if (pageUrl) { r.site.page = pageUrl; } + + if (bidderRequest.gppConsent) { + deepSetValue(r, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(r, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } } if (config.getConfig('coppa')) { deepSetValue(r, 'regs.coppa', 1); } - const payload = {}; + return r +} + +/** + * createPayload creates the payload to be sent with the request. + * + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {object} r Reuqest object. + * @param {string} baseUrl Base exchagne URL. + * @param {array} requests List of request obejcts. + * @param {object} payload Request payload object. + * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). + */ +function createPayload(validBidRequests, bidderRequest, r, baseUrl, requests, payload, MAX_REQUEST_SIZE) { // Use the siteId in the first bid request as the main siteId. siteID = validBidRequests[0].params.siteId; payload.s = siteID; - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_enable_post')) { - if (version) { - payload.v = version; - } - payload.ac = 'j'; - payload.sd = 1; - - if (version === VIDEO_ENDPOINT_VERSION) { - payload.nf = 1; - } - } - // Parse additional runtime configs. const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; const otherIxConfig = config.getConfig(bidderCode); - const requests = []; - let requestSequenceNumber = 0; - const transactionIds = Object.keys(impressions); - const baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; + + baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; if (baseRequestSize > MAX_REQUEST_SIZE) { logError('IX Bid Adapter: Base request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.EXCEEDS_MAX_SIZE }); return requests; } - let currentRequestSize = baseRequestSize; + currentRequestSize = baseRequestSize; let fpdRequestSize = 0; - let isFpdAdded = false; if (otherIxConfig) { // Append firstPartyData to r.site.page if firstPartyData exists. @@ -751,197 +906,198 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { fpdRequestSize = encodeURIComponent(firstPartyString).length; - if (fpdRequestSize < MAX_REQUEST_SIZE) { + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { + if (fpdRequestSize < MAX_REQUEST_SIZE) { + if ('page' in r.site) { + r.site.page += firstPartyString; + } else { + r.site.page = firstPartyString; + } + currentRequestSize += fpdRequestSize; + } else { + logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); + } + } else { if ('page' in r.site) { r.site.page += firstPartyString; } else { r.site.page = firstPartyString; } - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); - } - } - - if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_enable_post')) { - // Create t in payload if timeout is configured. - if (typeof otherIxConfig.timeout === 'number') { - payload.t = otherIxConfig.timeout; } } } +} - for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { - if (currentRequestSize >= MAX_REQUEST_SIZE || requests.length >= MAX_REQUEST_LIMIT) { - break; - } +/** + * addImpressions adds impressions to request object + * + * @param {array} impressions List of impressions to be added to the request. + * @param {array} transactionIds List of transaction Ids. + * @param {object} r Reuqest object. + * @param {int} adUnitIndex Index of the current add unit + * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). + * @return {object} Reqyest object with added impressions describing the request to the server. + */ +function addImpressions(impressions, transactionIds, r, adUnitIndex, MAX_REQUEST_SIZE) { + const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; + const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; - const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; - const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; - let wasAdUnitImpressionsTrimmed = false; - let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; - const sourceImpressions = { ixImps, missingBannerImpressions }; - const impressionObjects = Object.keys(sourceImpressions) - .map((key) => sourceImpressions[key]) - .filter(item => Array.isArray(item)) - .reduce((acc, curr) => acc.concat(...curr), []); + let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; + const sourceImpressions = { ixImps, missingBannerImpressions }; + const impressionObjects = Object.keys(sourceImpressions) + .map((key) => sourceImpressions[key]) + .filter(item => Array.isArray(item)) + .reduce((acc, curr) => acc.concat(...curr), []); - let currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; + currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { while (impressionObjects.length && currentImpressionSize > remainingRequestSize) { wasAdUnitImpressionsTrimmed = true; impressionObjects.pop(); currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; } + } - const gpid = impressions[transactionIds[adUnitIndex]].gpid; - const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; - const tid = impressions[transactionIds[adUnitIndex]].tid; - const sid = impressions[transactionIds[adUnitIndex]].sid - - if (impressionObjects.length && BANNER in impressionObjects[0]) { - const { id, banner: { topframe } } = impressionObjects[0]; - const _bannerImpression = { - id, - banner: { - topframe, - format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) - }, - }; - - // We add sid in imp.ext.sid therefore, remove from banner.format[].ext - for (let bannerFormat of _bannerImpression.banner.format) { - if (bannerFormat.ext != null && bannerFormat.ext.sid != null) { - delete bannerFormat.ext.sid; - } - } - - const position = impressions[transactionIds[adUnitIndex]].pos; - if (isInteger(position)) { - _bannerImpression.banner.pos = position; - } + const gpid = impressions[transactionIds[adUnitIndex]].gpid; + const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; + const tid = impressions[transactionIds[adUnitIndex]].tid; + const sid = impressions[transactionIds[adUnitIndex]].sid - if (dfpAdUnitCode || gpid || tid || sid) { - _bannerImpression.ext = {}; - _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; - _bannerImpression.ext.gpid = gpid; - _bannerImpression.ext.tid = tid; - _bannerImpression.ext.sid = sid; - } + if (impressionObjects.length && BANNER in impressionObjects[0]) { + const { id, banner: { topframe } } = impressionObjects[0]; + const _bannerImpression = { + id, + banner: { + topframe, + format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) + }, + }; - if ('bidfloor' in impressionObjects[0]) { - _bannerImpression.bidfloor = impressionObjects[0].bidfloor; + for (let i = 0; i < _bannerImpression.banner.format.length; i++) { + // We add sid in imp.ext.sid therefore, remove from banner.format[].ext + if (_bannerImpression.banner.format[i].ext != null && _bannerImpression.banner.format[i].ext.sid != null) { + delete _bannerImpression.banner.format[i].ext.sid; } - if ('bidfloorcur' in impressionObjects[0]) { - _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; + // add floor per size + if ('bidfloor' in impressionObjects[i]) { + _bannerImpression.banner.format[i].ext.bidfloor = impressionObjects[i].bidfloor; } + } - r.imp.push(_bannerImpression); - } else { - // set imp.ext.gpid to resolved gpid for each imp - impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); - r.imp.push(...impressionObjects); + const position = impressions[transactionIds[adUnitIndex]].pos; + if (isInteger(position)) { + _bannerImpression.banner.pos = position; } - currentRequestSize += currentImpressionSize; + if (dfpAdUnitCode || gpid || tid || sid) { + _bannerImpression.ext = {}; + _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; + _bannerImpression.ext.gpid = gpid; + _bannerImpression.ext.tid = tid; + _bannerImpression.ext.sid = sid; + } - const fpd = deepAccess(bidderRequest, 'ortb2') || {}; + if ('bidfloor' in impressionObjects[0]) { + _bannerImpression.bidfloor = impressionObjects[0].bidfloor; + } - if (!isEmpty(fpd) && !isFpdAdded) { - r.ext.ixdiag.fpd = true; + if ('bidfloorcur' in impressionObjects[0]) { + _bannerImpression.bidfloorcur = impressionObjects[0].bidfloorcur; + } - const site = { ...(fpd.site || fpd.context) }; + r.imp.push(_bannerImpression); + } else { + // set imp.ext.gpid to resolved gpid for each imp + impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); + r.imp.push(...impressionObjects); + } - Object.keys(site).forEach(key => { - if (FIRST_PARTY_DATA.SITE.indexOf(key) === -1) { - delete site[key]; - } - }); + return r; +} - const user = { ...fpd.user }; +/** + * addFPD adds ortb2 first party data to request object. + * + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {object} r Reuqest object. + * @param {object} fpd ortb2 first party data. + * @param {object} site First party site data. + * @param {object} user First party user data. + * @return {object} Reqyest object with added FPD describing the request to the server. + */ +function addFPD(bidderRequest, r, fpd, site, user) { + r.ext.ixdiag.fpd = true; - Object.keys(user).forEach(key => { - if (FIRST_PARTY_DATA.USER.indexOf(key) === -1) { - delete user[key]; - } - }); + Object.keys(site).forEach(key => { + if (FIRST_PARTY_DATA.SITE.indexOf(key) === -1) { + delete site[key]; + } + }); - const clonedRObject = deepClone(r); + Object.keys(user).forEach(key => { + if (FIRST_PARTY_DATA.USER.indexOf(key) === -1) { + delete user[key]; + } + }); - clonedRObject.site = mergeDeep({}, clonedRObject.site, site); - clonedRObject.user = mergeDeep({}, clonedRObject.user, user); + if (fpd.device) { + const sua = {...fpd.device.sua}; + if (!isEmpty(sua)) { + deepSetValue(r, 'device.sua', sua); + } + } - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { + if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { + deepSetValue(r, 'regs.gpp', fpd.regs.gpp) + } - if (requestSize < MAX_REQUEST_SIZE) { - r.site = mergeDeep({}, r.site, site); - r.user = mergeDeep({}, r.user, user); - isFpdAdded = true; - const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; - currentRequestSize += fpdRequestSize; - } else { - logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); - } + if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { + deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) } + } - // add identifiers info to ixDiag - const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; - const tagId = impressions[transactionIds[adUnitIndex]].tagId; - const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; - const divId = impressions[transactionIds[adUnitIndex]].divId; - if (pbaAdSlot || tagId || adUnitCode || divId) { - const clonedRObject = deepClone(r); - const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + return r; +} + +/** + * addIdentifiersInfo adds indentifier info to ixDaig. + * + * @param {array} impressions List of impressions to be added to the request. + * @param {object} r Reuqest object. + * @param {array} transactionIds List of transaction Ids. + * @param {int} adUnitIndex Index of the current add unit + * @param {object} payload Request payload object. + * @param {string} baseUrl Base exchagne URL. + * @param {int} MAX_REQUEST_SIZE Maximum request size limit (buildrequest V1). + * @return {object} Reqyest object with added indentigfier info to ixDiag. + */ +function addIdentifiersInfo(impressions, r, transactionIds, adUnitIndex, payload, baseUrl, MAX_REQUEST_SIZE) { + const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; + const tagId = impressions[transactionIds[adUnitIndex]].tagId; + const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; + const divId = impressions[transactionIds[adUnitIndex]].divId; + if (pbaAdSlot || tagId || adUnitCode || divId) { + const clonedRObject = deepClone(r); + const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + if (!FEATURE_TOGGLES.isFeatureEnabled('pbjs_use_buildRequestV2')) { if (requestSize < MAX_REQUEST_SIZE) { r.ext.ixdiag.pbadslot = pbaAdSlot; r.ext.ixdiag.tagid = tagId; r.ext.ixdiag.adunitcode = adUnitCode; r.ext.ixdiag.divId = divId; } - } - - const isLastAdUnit = adUnitIndex === transactionIds.length - 1; - - if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { - const clonedPayload = deepClone(payload); - if (!isLastAdUnit || requestSequenceNumber) { - r.ext.ixdiag.sn = requestSequenceNumber; - clonedPayload.sn = requestSequenceNumber; - } - - requestSequenceNumber++; - clonedPayload.r = JSON.stringify(r); - - let requestData; - - if (FEATURE_TOGGLES.isFeatureEnabled('pbjs_enable_post')) { - requestData = { - method: 'POST', - url: baseUrl + '?s=' + payload.s, - data: deepClone(r), - option: { - contentType: 'text/plain', - }, - validBidRequests - } - } else { - requestData = { - method: 'GET', - url: baseUrl, - data: clonedPayload, - validBidRequests - } - } - requests.push(requestData); - - currentRequestSize = baseRequestSize; - r.imp = []; - isFpdAdded = false; + } else { + r.ext.ixdiag.pbadslot = pbaAdSlot; + r.ext.ixdiag.tagid = tagId; + r.ext.ixdiag.adunitcode = adUnitCode; + r.ext.ixdiag.divId = divId; } } - return requests; + return r; } /** @@ -1447,7 +1603,7 @@ export const spec = { // Step 1: Create impresssions from IX params validBidRequests.forEach((validBidRequest) => { - const adUnitMediaTypes = Object.keys(deepAccess(validBidRequest, 'mediaTypes', {})) + const adUnitMediaTypes = Object.keys(deepAccess(validBidRequest, 'mediaTypes', {})); for (const type in adUnitMediaTypes) { switch (adUnitMediaTypes[type]) { @@ -1527,13 +1683,7 @@ export const spec = { // Transform rawBid in bid response to the format that will be accepted by prebid. const innerBids = seatbid[i].bid; - let requestBid; - - if (FEATURE_TOGGLES.isFeatureEnabled('pbjs_enable_post')) { - requestBid = bidderRequest.data; - } else { - requestBid = safeJSONParse(bidderRequest.data.r); - } + const requestBid = bidderRequest.data; for (let j = 0; j < innerBids.length; j++) { const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp, bidderRequest.validBidRequests); diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index f6f58883990..5eeab197f3a 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -56,6 +56,8 @@ function fetchIds_() { if (tmp) ret.client_id_ls = tmp; tmp = storage.getDataFromLocalStorage('_jxxs'); if (tmp) ret.session_id_ls = tmp; + tmp = storage.getCookie('_jxtoko'); + if (tmp) ret.jxtoko_id = tmp; } catch (error) {} return ret; } diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 342531ba26e..b79843dccfd 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -274,8 +274,7 @@ export function getContentSegments(segments) { const formattedSegments = segments.reduce((convertedSegments, rawSegment) => { convertedSegments.push({ - id: rawSegment, - value: rawSegment + id: rawSegment }); return convertedSegments; }, []); diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js new file mode 100644 index 00000000000..6264522ad83 --- /dev/null +++ b/modules/jwplayerVideoProvider.js @@ -0,0 +1,936 @@ +import { + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION +} from '../libraries/video/constants/ortb.js'; +import { + SETUP_COMPLETE, SETUP_FAILED, DESTROYED, AD_REQUEST, AD_BREAK_START, AD_LOADED, AD_STARTED, AD_IMPRESSION, AD_PLAY, + AD_TIME, AD_PAUSE, AD_CLICK, AD_SKIPPED, AD_ERROR, AD_COMPLETE, AD_BREAK_END, PLAYLIST, PLAYBACK_REQUEST, + AUTOSTART_BLOCKED, PLAY_ATTEMPT_FAILED, CONTENT_LOADED, PLAY, PAUSE, BUFFER, TIME, SEEK_START, SEEK_END, MUTE, VOLUME, + RENDITION_UPDATE, ERROR, COMPLETE, PLAYLIST_COMPLETE, FULLSCREEN, PLAYER_RESIZE, VIEWABLE, CAST +} from '../libraries/video/constants/events.js'; +import { PLAYBACK_MODE } from '../libraries/video/constants/constants.js'; +import stateFactory from '../libraries/video/shared/state.js'; +import { JWPLAYER_VENDOR } from '../libraries/video/constants/vendorCodes.js'; +import { getEventHandler } from '../libraries/video/shared/eventHandler.js'; +import { submodule } from '../src/hook.js'; + +/** + * @constructor + * @param {videoProviderConfig} config + * @param {Object} jwplayer_ - JW Player global factory + * @param {State} adState_ + * @param {State} timeState_ + * @param {CallbackStorage} callbackStorage_ + * @param {Object} utils + * @returns {VideoProvider} + */ +export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callbackStorage_, utils, sharedUtils) { + const jwplayer = jwplayer_; + let player = null; + let playerVersion = null; + const playerConfig = config.playerConfig; + const divId = config.divId; + let adState = adState_; + let timeState = timeState_; + let callbackStorage = callbackStorage_; + let pendingSeek = {}; + let supportedMediaTypes = null; + let minimumSupportedPlayerVersion = '8.20.1'; + let setupCompleteCallbacks = []; + let setupFailedCallbacks = []; + const MEDIA_TYPES = [ + VIDEO_MIME_TYPE.MP4, + VIDEO_MIME_TYPE.OGG, + VIDEO_MIME_TYPE.WEBM, + VIDEO_MIME_TYPE.AAC, + VIDEO_MIME_TYPE.HLS + ]; + + function init() { + if (!jwplayer) { + triggerSetupFailure(-1); // TODO: come up with error code schema- player is absent + return; + } + + playerVersion = jwplayer.version; + + if (playerVersion < minimumSupportedPlayerVersion) { + triggerSetupFailure(-2); // TODO: come up with error code schema - version not supported + return; + } + + if (!document.getElementById(divId)) { + triggerSetupFailure(-3); // TODO: come up with error code schema - missing div id + return; + } + + player = jwplayer(divId); + if (!player || !player.getState) { + triggerSetupFailure(-4); // TODO: come up with error code schema - factory function failure + } else if (player.getState() === undefined) { + setupPlayer(playerConfig); + } else { + const payload = getSetupCompletePayload(); + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; + } + } + + function getId() { + return divId; + } + + function getOrtbVideo() { + if (!player) { + return; + } + + const config = player.getConfig() || {}; + const adConfig = config.advertising || {}; + supportedMediaTypes = supportedMediaTypes || utils.getSupportedMediaTypes(MEDIA_TYPES); + + const video = { + mimes: supportedMediaTypes, + protocols: [ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ], + h: player.getHeight(), // TODO does player call need optimization ? + w: player.getWidth(), // TODO does player call need optimization ? + startdelay: utils.getStartDelay(), + placement: utils.getPlacement(adConfig, player), + // linearity is omitted because both forms are supported. + // sequence - TODO not yet supported + battr: adConfig.battr, + maxextended: -1, // extension is allowed, and there is no time limit imposed. + boxingallowed: 1, + playbackmethod: [ utils.getPlaybackMethod(config) ], + playbackend: 1, // TODO: need to account for floating player - https://developer.jwplayer.com/jwplayer/docs/jw8-embed-an-outstream-player , https://developer.jwplayer.com/jwplayer/docs/jw8-player-configuration-reference#section-float-on-scroll + // companionad - TODO add in future version + // companiontype - TODO add in future version + // minbitrate - TODO add in future version + // maxbitrate - TODO add in future version + // delivery - omitted because all are supported. + // minduration - Is there value to specifying ? + // maxduration - Is there value to specifying ? + api: [ + API_FRAMEWORKS.VPAID_2_0 + ], + }; + + if (utils.isOmidSupported(adConfig.adClient)) { + video.api.push(API_FRAMEWORKS.OMID_1_0); + } + + Object.assign(video, utils.getSkipParams(adConfig)); + + if (player.getFullscreen()) { // TODO does player call need optimization ? + // only specify ad position when in Fullscreen since computational cost is low + // ad position options are listed in oRTB 2.5 section 5.4 + // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf + video.pos = AD_POSITION.FULL_SCREEN; // TODO make constant in oRTB + } + + return video; + } + + function getOrtbContent() { + if (!player) { + return; + } + + const item = player.getPlaylistItem() || {}; // TODO does player call need optimization ? + let { duration, playbackMode } = timeState.getState(); + if (duration === undefined) { + duration = player.getDuration(); + } + + const content = { + url: item.file, + title: item.title, + cat: item.iabCategories, + keywords: item.tags, + len: duration, + embeddable: 1 + }; + + if (playbackMode !== undefined) { + content.livestream = Math.min(playbackMode, 1); + } + + const mediaId = item.mediaid; + if (mediaId) { + content.id = 'jw_' + mediaId; + } + + const jwpseg = item.jwpseg; + const dataSegment = utils.getSegments(jwpseg); + const contentDatum = utils.getContentDatum(mediaId, dataSegment); + if (contentDatum) { + content.data = [contentDatum]; + } + + const isoLanguageCode = utils.getIsoLanguageCode(player); + if (isoLanguageCode) { + content.language = isoLanguageCode; + } + + return content; + } + + function setAdTagUrl(adTagUrl, options) { + if (!player) { + return; + } + + player.playAd(adTagUrl || options.adXml, options); + } + + function onEvent(externalEventName, callback, basePayload) { + if (externalEventName === SETUP_COMPLETE) { + setupCompleteCallbacks.push(callback); + } else if (externalEventName === SETUP_FAILED) { + setupFailedCallbacks.push(callback); + } + + if (!player) { + return; + } + + let getEventPayload; + + switch (externalEventName) { + case SETUP_COMPLETE: + getEventPayload = () => { + setupCompleteCallbacks = []; + return getSetupCompletePayload(); + }; + break; + + case SETUP_FAILED: + getEventPayload = e => { + setupFailedCallbacks = []; + return { + playerVersion, + errorCode: e.code, + errorMessage: e.message, + sourceError: e.sourceError + }; + }; + break; + + case AD_REQUEST: + case AD_PLAY: + case AD_PAUSE: + getEventPayload = e => ({ adTagUrl: e.tag }); + break; + + case AD_BREAK_START: + getEventPayload = e => { + timeState.clearState(); + return { offset: e.adPosition }; + }; + break; + + case AD_LOADED: + getEventPayload = e => { + adState.updateForEvent(e); + const adConfig = player.getConfig().advertising; + adState.updateState(utils.getSkipParams(adConfig)); + return adState.getState(); + }; + break; + + case AD_STARTED: + // JW Player adImpression fires when the ad starts, regardless of viewability. + getEventPayload = () => adState.getState(); + break; + + case AD_IMPRESSION: + case AD_CLICK: + getEventPayload = () => Object.assign({}, adState.getState(), timeState.getState()); + break; + + case AD_TIME: + getEventPayload = e => { + timeState.updateForEvent(e); + return { + adTagUrl: e.tag, + time: e.position, + duration: e.duration, + }; + }; + break; + + case AD_SKIPPED: + getEventPayload = e => { + adState.clearState(); + return { + time: e.position, + duration: e.duration, + }; + }; + break; + + case AD_ERROR: + getEventPayload = e => { + const extraPayload = Object.assign({ + playerErrorCode: e.adErrorCode, + vastErrorCode: e.code, + errorMessage: e.message, + sourceError: e.sourceError + // timeout + }, adState.getState(), timeState.getState()); + adState.clearState(); + return extraPayload; + }; + break; + + case AD_COMPLETE: + getEventPayload = e => { + adState.clearState(); + return { adTagUrl: e.tag }; + }; + break; + + case AD_BREAK_END: + getEventPayload = e => ({ offset: e.adPosition }); + break; + + case PLAYLIST: + getEventPayload = e => { + const playlistItemCount = e.playlist.length; + return { + playlistItemCount, + autostart: player.getConfig().autostart + }; + }; + break; + + case PLAYBACK_REQUEST: + getEventPayload = e => ({ playReason: e.playReason }); + break; + + case AUTOSTART_BLOCKED: + getEventPayload = e => ({ + sourceError: e.error, + errorCode: e.code, + errorMessage: e.message + }); + break; + + case PLAY_ATTEMPT_FAILED: + getEventPayload = e => ({ + playReason: e.playReason, + sourceError: e.sourceError, + errorCode: e.code, + errorMessage: e.message + }); + break; + + case CONTENT_LOADED: + getEventPayload = e => { + const { item, index } = e; + return { + contentId: item.mediaid, + contentUrl: item.file, // cover other sources ? util ? + title: item.title, + description: item.description, + playlistIndex: index, + contentTags: item.tags + }; + }; + break; + + case BUFFER: + getEventPayload = () => timeState.getState(); + break; + + case TIME: + getEventPayload = e => { + timeState.updateForEvent(e); + return { + position: e.position, + duration: e.duration + }; + }; + break; + + case SEEK_START: + getEventPayload = e => { + const duration = e.duration; + const offset = e.offset; + pendingSeek = { + duration, + offset + }; + return { + position: e.position, + destination: offset, + duration: duration + }; + } + break; + + case SEEK_END: + getEventPayload = () => { + const extraPayload = { + position: pendingSeek.offset, + duration: pendingSeek.duration + }; + pendingSeek = {}; + return extraPayload; + }; + break; + + case MUTE: + getEventPayload = e => ({ mute: e.mute }); + break; + + case VOLUME: + getEventPayload = e => ({ volumePercentage: e.volume }); + break; + + case RENDITION_UPDATE: + getEventPayload = e => { + const bitrate = e.bitrate; + const level = e.level; + return { + videoReportedBitrate: bitrate, + audioReportedBitrate: bitrate, + encodedVideoWidth: level.width, + encodedVideoHeight: level.height, + videoFramerate: e.frameRate + }; + }; + break; + + case ERROR: + getEventPayload = e => ({ + sourceError: e.sourceError, + errorCode: e.code, + errorMessage: e.message, + }); + break; + + case COMPLETE: + getEventPayload = e => timeState.clearState(); + break; + + case FULLSCREEN: + getEventPayload = e => ({ fullscreen: e.fullscreen }); + break; + + case PLAYER_RESIZE: + getEventPayload = e => ({ + height: e.height, + width: e.width, + }); + break; + + case VIEWABLE: + getEventPayload = e => ({ + viewable: e.viewable, + viewabilityPercentage: player.getPercentViewable() * 100, + }); + break; + + case CAST: + getEventPayload = e => ({ casting: e.active }); + break; + + case PLAY: + case PAUSE: + case PLAYLIST_COMPLETE: + case DESTROYED: + break; + + default: + return; + } + + const jwEventName = utils.getJwEvent(externalEventName); + const eventHandler = getEventHandler(externalEventName, callback, basePayload, getEventPayload) + player.on(jwEventName, eventHandler); + callbackStorage.storeCallback(externalEventName, eventHandler, callback); + } + + function offEvent(event, callback) { + const jwEvent = utils.getJwEvent(event); + if (!callback) { + player.off(jwEvent); + return; + } + + const eventHandler = callbackStorage.getCallback(event, callback); + if (!eventHandler) { + return; + } + + player.off(jwEvent, eventHandler); + } + + function destroy() { + if (!player) { + return; + } + player.remove(); + player = null; + } + + return { + init, + getId, + getOrtbVideo, + getOrtbContent, + setAdTagUrl, + onEvent, + offEvent, + destroy + }; + + function setupPlayer(config) { + if (!config) { + return; + } + player.setup(utils.getJwConfig(config)); + } + + function getSetupCompletePayload() { + return { + divId, + playerVersion, + type: SETUP_COMPLETE, + viewable: player.getViewable(), + viewabilityPercentage: player.getPercentViewable() * 100, + mute: player.getMute(), + volumePercentage: player.getVolume() + }; + } + + function triggerSetupFailure(errorCode) { + if (!setupFailedCallbacks.length) { + return; + } + + const payload = { + divId, + playerVersion, + type: SETUP_FAILED, + errorCode, + errorMessage: '', + sourceError: null + }; + + setupFailedCallbacks.forEach(callback => callback(SETUP_FAILED, payload)); + setupFailedCallbacks = []; + } +} + +/** + * @param {videoProviderConfig} config + * @param {sharedUtils} sharedUtils + * @returns {VideoProvider} + */ +const jwplayerSubmoduleFactory = function (config, sharedUtils) { + const adState = adStateFactory(); + const timeState = timeStateFactory(); + const callbackStorage = callbackStorageFactory(); + return JWPlayerProvider(config, window.jwplayer, adState, timeState, callbackStorage, utils, sharedUtils); +} + +jwplayerSubmoduleFactory.vendorCode = JWPLAYER_VENDOR; +submodule('video', jwplayerSubmoduleFactory); +export default jwplayerSubmoduleFactory; + +// HELPERS + +export const utils = { + getJwConfig: function(config) { + if (!config) { + return; + } + + const params = config.params || {}; + const jwConfig = params.vendorConfig || {}; + if (jwConfig.autostart === undefined && config.autoStart !== undefined) { + jwConfig.autostart = config.autoStart; + } + + if (jwConfig.mute === undefined && config.mute !== undefined) { + jwConfig.mute = config.mute; + } + + if (!jwConfig.key && config.licenseKey !== undefined) { + jwConfig.key = config.licenseKey; + } + + if (config.setupAds === false) { + return jwConfig; + } + + const advertising = jwConfig.advertising || { client: 'vast' }; + if (!jwConfig.file && !jwConfig.playlist && !jwConfig.source) { + // TODO verify accuracy + advertising.outstream = true; + } + + const bids = advertising.bids || {}; + bids.prebid = true; + advertising.bids = bids; + + jwConfig.advertising = advertising; + return jwConfig; + }, + + getJwEvent: function(eventName) { + switch (eventName) { + case SETUP_COMPLETE: + return 'ready'; + + case SETUP_FAILED: + return 'setupError'; + + case DESTROYED: + return 'remove'; + + case AD_STARTED: + return AD_IMPRESSION; + + case AD_IMPRESSION: + return 'adViewableImpression'; + + case PLAYBACK_REQUEST: + return 'playAttempt'; + + case AUTOSTART_BLOCKED: + return 'autostartNotAllowed'; + + case CONTENT_LOADED: + return 'playlistItem'; + + case SEEK_START: + return 'seek'; + + case SEEK_END: + return 'seeked'; + + case RENDITION_UPDATE: + return 'visualQuality'; + + case PLAYER_RESIZE: + return 'resize'; + + default: + return eventName; + } + }, + + getSkipParams: function(adConfig) { + const skipParams = {}; + const skipoffset = adConfig.skipoffset; + if (skipoffset !== undefined) { + const skippable = skipoffset >= 0; + skipParams.skip = skippable ? 1 : 0; + if (skippable) { + skipParams.skipmin = skipoffset + 2; + skipParams.skipafter = skipoffset; + } + } + return skipParams; + }, + + getSupportedMediaTypes: function(mediaTypes = []) { + const el = document.createElement('video'); + return mediaTypes + .filter(mediaType => el.canPlayType(mediaType)) + .concat(VPAID_MIME_TYPE); // Always allow VPAIDs. + }, + + getStartDelay: function() { + // todo calculate + // need to know which ad we are bidding on + // Might have to implement and set in Pb-video ; would required ad unit as param. + }, + + /** + * Determine the ad placement + * @param {Object} adConfig + * @param {Object} player + * @return {PLACEMENT|OrtbVideoParams.placement|undefined} + */ + getPlacement: function(adConfig, player) { + if (!adConfig.outstream) { + // https://developer.jwplayer.com/jwplayer/docs/jw8-embed-an-outstream-player for more info on outstream + return PLACEMENT.INSTREAM; + } + + if (player.getFloating()) { + return PLACEMENT.FLOATING; + } + + const placement = adConfig.placement; + if (!placement) { + return; + } + + return PLACEMENT[placement.toUpperCase()]; + }, + + getPlaybackMethod: function({ autoplay, mute, autoplayAdsMuted }) { + if (autoplay) { + // Determine whether player is going to start muted. + const isMuted = mute || autoplayAdsMuted; // todo autoplayAdsMuted only applies to preRoll + return isMuted ? PLAYBACK_METHODS.AUTOPLAY_MUTED : PLAYBACK_METHODS.AUTOPLAY; + } + /* + TODO + could support the following with float player: + 5 Initiates on Entering Viewport with Sound On + 6 Initiates on Entering Viewport with Sound Off by Default + */ + return PLAYBACK_METHODS.CLICK_TO_PLAY; + }, + + /** + * Indicates if Omid is supported + * + * @param {string} adClient - The identifier of the ad plugin requesting the bid + * @returns {boolean} - support of omid + */ + isOmidSupported: function(adClient) { + const omidIsLoaded = window.OmidSessionClient !== undefined; + return omidIsLoaded && adClient === 'vast'; + }, + + /** + * Gets ISO 639 language code of current audio track. + * @param {Object} player + * @returns {string|undefined} ISO 639 language code. + */ + getIsoLanguageCode: function(player) { + const audioTracks = player.getAudioTracks(); + if (!audioTracks || !audioTracks.length) { + return; + } + + const currentTrackIndex = Math.max(player.getCurrentAudioTrack() || 0, 0); // returns -1 when there are no alternative tracks. + const audioTrack = audioTracks[currentTrackIndex]; + return audioTrack && audioTrack.language; + }, + + /** + * Converts an array of jwpsegs into an array of data segments compliant with the oRTB content.data[index].segment + * @param {[String]} jwpsegs - jwplayer contextual targeting segments + * @return {[Object]|undefined} list of data segments compliant with the oRTB content.data[index].segment spec + */ + getSegments: function (jwpsegs) { + if (!jwpsegs || !jwpsegs.length) { + return; + } + + const formattedSegments = jwpsegs.reduce((convertedSegments, rawSegment) => { + convertedSegments.push({ + id: rawSegment, + value: rawSegment + }); + return convertedSegments; + }, []); + + return formattedSegments; + }, + + /** + * Creates an object compliant with the oRTB content.data[index] spec. + * @param {String} mediaId - content identifier + * @param {[Object]} segments - list of data segments compliant with the oRTB content.data[index].segment spec + * @return {Object} - Object compliant with the oRTB content.data[index] spec. + */ + getContentDatum: function (mediaId, segments) { + if (!mediaId && !segments) { + return; + } + + const contentData = { + name: 'jwplayer.com', + ext: {} + }; + + if (mediaId) { + contentData.ext.cids = [mediaId]; + } + + if (segments) { + contentData.segment = segments; + contentData.ext.segtax = 502; + } + + return contentData; + } +} + +/** + * Tracks which functions are attached to events + * @typedef CallbackStorage + * @function storeCallback + * @function getCallback + * @function clearStorage + */ + +/** + * @returns {CallbackStorage} + */ +export function callbackStorageFactory() { + let storage = {}; + + function storeCallback(eventType, eventHandler, callback) { + let eventHandlers = storage[eventType]; + if (!eventHandlers) { + eventHandlers = storage[eventType] = {}; + } + + eventHandlers[callback] = eventHandler; + } + + function getCallback(eventType, callback) { + let eventHandlers = storage[eventType]; + if (!eventHandlers) { + return; + } + + const eventHandler = eventHandlers[callback]; + delete eventHandlers[callback]; + return eventHandler; + } + + function clearStorage() { + storage = {}; + } + + return { + storeCallback, + getCallback, + clearStorage + } +} + +// STATE + +/** + * @returns {State} + */ +export function adStateFactory() { + const adState = Object.assign({}, stateFactory()); + + function updateForEvent(event) { + const updates = { + adTagUrl: event.tag, + offset: event.adPosition, + loadTime: event.timeLoading, + vastAdId: event.id, // TODO: delete from spec!! seems JW Player specific + adDescription: event.description, + adServer: event.adsystem, + adTitle: event.adtitle, + advertiserId: event.advertiserId, + advertiserName: event.advertiser, + dealId: event.dealId, + // adCategories + linear: event.linear, + vastVersion: event.vastversion, + // campaignId: + creativeUrl: event.mediaFile, // TODO: per AP, mediafile might be object w/ file property. verify + adId: event.adId, + universalAdId: event.universalAdId, + creativeId: event.creativeAdId, + creativeType: event.creativetype, + redirectUrl: event.clickThroughUrl, + adPlacementType: convertPlacementToOrtbCode(event.placement), + waterfallIndex: event.witem, + waterfallCount: event.wcount, + adPodCount: event.podcount, + adPodIndex: event.sequence, + wrapperAdIds: event.wrapperAdIds + }; + + if (event.client === 'googima' && !updates.wrapperAdIds) { + updates.wrapperAdIds = parseImaAdWrapperIds(event); + } + + this.updateState(updates); + } + + adState.updateForEvent = updateForEvent; + + function convertPlacementToOrtbCode(placement) { + switch (placement) { + case 'instream': + return PLACEMENT.INSTREAM; + + case 'banner': + return PLACEMENT.BANNER; + + case 'article': + return PLACEMENT.ARTICLE; + + case 'feed': + return PLACEMENT.FEED; + + case 'interstitial': + case 'slider': + case 'floating': + return PLACEMENT.INTERSTITIAL_SLIDER_FLOATING; + } + } + + function parseImaAdWrapperIds(adEvent) { + const ima = adEvent.ima; + const ad = ima && ima.ad; + if (!ad) { + return; + } + + const adProperties = Object.keys(ad); + adProperties.forEach(property => { + const value = ad[property]; + const wrapperIds = value.adWrapperIds; + if (wrapperIds) { + return wrapperIds; + } + }); + } + + return adState; +} + +/** + * @returns {State} + */ +export function timeStateFactory() { + const timeState = Object.assign({}, stateFactory()); + + function updateForEvent(event) { + const { position, duration } = event; + this.updateState({ + time: position, + duration, + playbackMode: getPlaybackMode(duration) + }); + } + + timeState.updateForEvent = updateForEvent; + + function getPlaybackMode(duration) { + if (duration > 0) { + return PLAYBACK_MODE.VOD; + } else if (duration < 0) { + return PLAYBACK_MODE.DVR; + } + + return PLAYBACK_MODE.LIVE; + } + + return timeState; +} diff --git a/modules/jwplayerVideoProvider.md b/modules/jwplayerVideoProvider.md new file mode 100644 index 00000000000..54a9c1fb84d --- /dev/null +++ b/modules/jwplayerVideoProvider.md @@ -0,0 +1,25 @@ +# Overview + +Module Name: JW Player Video Provider +Module Type: Video Submodule +Video Player: JW player +Player website: https://www.jwplayer.com/html5-video-player/ +Maintainer: karim@jwplayer.com + +# Description + +Video provider to connect the Prebid Video Module to JW Player. + +# Requirements + +Your page must embed a build of JW Player. +i.e. +```html + + + +``` + +[Additional embed instructions](https://docs.jwplayer.com/platform/docs/players-get-started) + +[Obtaining a license](https://info.jwplayer.com/contact-us/) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index f4563081aa7..6a1c1cf94b0 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,4 +1,4 @@ -import { _each, buildUrl, triggerPixel } from '../src/utils.js'; +import { _each, buildUrl, deepAccess, pick, triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -37,10 +37,9 @@ export const spec = { bidSizes[bid.bidId] = bid.sizes; }); - let tdid; - if (validBidRequests.length > 0 && validBidRequests[0].userId && validBidRequests[0].userId.tdid) { - tdid = validBidRequests[0].userId.tdid; - } + const firstBidRequest = validBidRequests[0]; + + const tdid = deepAccess(firstBidRequest, 'userId.tdid') const transformedParams = Object.assign({}, { sessionId: spec._getSessionId(), @@ -62,10 +61,17 @@ export const spec = { prebidRawBidRequests: validBidRequests }, spec._getAllMetadata(bidderRequest, tdid)); + // User Agent Client Hints / SUA + const uaClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); + if (uaClientHints) { + transformedParams.device.sua = pick(uaClientHints, ['browsers', 'platform', 'mobile', 'model']); + } + // Pull Social Canvas segments and embed URL - if (validBidRequests.length > 0 && validBidRequests[0].params.socialCanvas) { - transformedParams.socialCanvasSegments = validBidRequests[0].params.socialCanvas.segments; - transformedParams.socialEmbedURL = validBidRequests[0].params.socialCanvas.embedURL; + const socialCanvas = deepAccess(firstBidRequest, 'params.socialCanvas'); + if (socialCanvas) { + transformedParams.socialCanvasSegments = socialCanvas.segments; + transformedParams.socialEmbedURL = socialCanvas.embedURL; } const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); @@ -110,7 +116,11 @@ export const spec = { }; if (meta.mediaType == VIDEO) { - bidResponse.vastXml = adUnit.adm; + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; + } } bidResponses.push(bidResponse); @@ -225,7 +235,7 @@ export const spec = { _getAllMetadata(bidderRequest, tdid) { return { userIDs: spec._getUserIds(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent), - pageURL: bidderRequest.refererInfo && bidderRequest.refererInfo.page, + pageURL: bidderRequest?.refererInfo?.page, rawCRB: storage.getCookie('krg_crb'), rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') }; diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js new file mode 100644 index 00000000000..821b3263597 --- /dev/null +++ b/modules/kueezRtbBidAdapter.js @@ -0,0 +1,325 @@ +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { config } from '../src/config.js'; + +const GVLID = 1165; +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'kueezrtb'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +export const SUPPORTED_ID_SYSTEMS = { + 'britepoolid': 1, + 'criteoId': 1, + 'id5id': 1, + 'idl_env': 1, + 'lipb': 1, + 'netId': 1, + 'parrableId': 1, + 'pubcid': 1, + 'tdid': 1, + 'pubProvidedId': 1 +}; +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.kueezrtb.com`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout + }; + + appendUserIdsToRequestPayload(data, userId); + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + + _each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {bidId} = request.data; + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach(result => { + const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + meta: { + advertiserDomains: advertiserDomains || [] + } + }; + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.kueezrtb.com/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.kueezrtb.com/api/sync/image/${params}` + }); + } + return syncs; +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { + } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/kueezRtbBidAdapter.md b/modules/kueezRtbBidAdapter.md new file mode 100644 index 00000000000..52d11aa362a --- /dev/null +++ b/modules/kueezRtbBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** Kueez RTB Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** rtb@kueez.com + +# Description + +Module that connects to Kueez's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'kueezrtb', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js index da48bc460c7..ad25cbe1e85 100644 --- a/modules/lassoBidAdapter.js +++ b/modules/lassoBidAdapter.js @@ -40,21 +40,21 @@ export const spec = { auctionId: bidRequest.auctionId, bidId: bidRequest.bidId, transactionId: bidRequest.transactionId, - device: JSON.stringify(getDeviceData()), + device: encodeURIComponent(JSON.stringify(getDeviceData())), sizes, aimXR, uid: '$UID', params: JSON.stringify(bidRequest.params), crumbs: JSON.stringify(bidRequest.crumbs), prebidVersion: '$prebid.version$', - version: 2, + version: 3, coppa: config.getConfig('coppa') == true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined } return { method: 'GET', - url: getBidRequestUrl(aimXR), + url: getBidRequestUrl(aimXR, bidRequest.params), data: payload, options: { withCredentials: true @@ -113,11 +113,15 @@ export const spec = { supportedMediaTypes: [BANNER] } -function getBidRequestUrl(aimXR) { +function getBidRequestUrl(aimXR, params) { + let path = '/request'; + if (params && params.dtc) { + path = '/dtc-request'; + } if (!aimXR) { - return GET_IUD_URL + ENDPOINT_URL + '/request'; + return GET_IUD_URL + ENDPOINT_URL + path; } - return ENDPOINT_URL + '/request' + return ENDPOINT_URL + path; } function getDeviceData() { diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index c39fd36e597..601d78578b3 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -26,7 +26,7 @@ function isBidResponseValid(bid) { export const spec = { code: BIDDER_CODE, - aliases: ['pll'], + aliases: ['pll', 'iionads'], supportedMediaTypes: [BANNER, VIDEO], /** @@ -158,7 +158,12 @@ function buildPlacement(bidRequest) { type: bidRequest.params.adUnitType.toUpperCase(), publisherId: bidRequest.params.publisherId, userIdAsEids: bidRequest.userIdAsEids, - supplyChain: bidRequest.schain + supplyChain: bidRequest.schain, + custom1: bidRequest.params.custom1, + custom2: bidRequest.params.custom2, + custom3: bidRequest.params.custom3, + custom4: bidRequest.params.custom4, + custom5: bidRequest.params.custom5 } } } diff --git a/modules/limelightDigitalBidAdapter.md b/modules/limelightDigitalBidAdapter.md index a4abb6f1411..2c773859a7f 100644 --- a/modules/limelightDigitalBidAdapter.md +++ b/modules/limelightDigitalBidAdapter.md @@ -24,7 +24,12 @@ var adUnits = [{ params: { host: 'exchange-9qao.ortb.net', adUnitId: 0, - adUnitType: 'banner' + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } }] }]; @@ -40,7 +45,12 @@ var videoAdUnit = [{ params: { host: 'exchange-9qao.ortb.net', adUnitId: 0, - adUnitType: 'video' + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' } }] }]; diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 724bf5ece6a..9f45daeea29 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,10 +7,9 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js/esm/initializer.js'; +import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; -import { MinimalLiveConnect } from 'live-connect-js/esm/minimal-live-connect.js'; const MODULE_NAME = 'liveIntentId'; export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}); @@ -140,7 +139,7 @@ export const liveIntentIdSubmodule = { this.moduleMode = mode }, getInitializer() { - return this.moduleMode === 'minimal' ? MinimalLiveConnect : LiveConnect + return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode) }, /** diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 3839eb80b91..af754ae3ff0 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep} from '../src/utils.js'; +import {deepAccess, getWindowTop, isSafariBrowser, mergeDeep, isFn, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; @@ -68,7 +68,9 @@ export const spec = { bidUrl = bidUrl ? bidUrl.params.bidUrl : URL; url = url ? url.params.url : (getAppDomain() || getTopWindowLocation(bidderRequest)); test = test ? test.params.test : undefined; - var adRequests = bidRequests.map(bidToAdRequest); + const currency = config.getConfig('currency.adServerCurrency') || 'USD'; + var adRequests = bidRequests.map(b => bidToAdRequest(b, currency)); + const adRequestsContainFloors = adRequests.some(r => r.flr !== undefined); if (eids) { ortb2 = mergeDeep(mergeDeep({}, ortb2 || {}), eids); @@ -96,7 +98,8 @@ export const spec = { rcv: getAdblockerRecovered(), adRequests: [...adRequests], rtbData: ortb2, - schain: schain + schain: schain, + flrCur: adRequestsContainFloors ? currency : undefined }; if (config.getConfig().debug) { @@ -223,13 +226,14 @@ function hasPubcid(bid) { return !!bid.crumbs && !!bid.crumbs.pubcid; } -function bidToAdRequest(bid) { +function bidToAdRequest(bid, currency) { var adRequest = { adUnitId: bid.params.adUnitId, callerAdUnitId: bid.params.adUnitName || bid.adUnitCode || bid.placementCode, bidId: bid.bidId, transactionId: bid.transactionId, formats: getSizes(bid).map(sizeToFormat), + flr: getBidFloor(bid, currency), options: bid.params.options }; @@ -264,6 +268,22 @@ function sizeToFormat(size) { } } +function getBidFloor(bid, currency) { + if (!isFn(bid.getFloor)) { + return undefined; + } + + const floor = bid.getFloor({ + currency: currency, + mediaType: '*', + size: '*' + }); + + return isPlainObject(floor) && !isNaN(floor.floor) && floor.currency == currency + ? floor.floor + : undefined; +} + function getAdblockerRecovered() { try { return getWindowTop().I12C && getWindowTop().I12C.Morph === 1; @@ -302,21 +322,13 @@ function getDeviceIfa() { } function getDeviceWidth() { - let device = config.getConfig('device'); - if (typeof device === 'object' && device.width) { - return device.width; - } - - return window.innerWidth; + const device = config.getConfig('device') || {}; + return device.w || window.innerWidth; } function getDeviceHeight() { - let device = config.getConfig('device'); - if (typeof device === 'object' && device.height) { - return device.height; - } - - return window.innerHeight; + const device = config.getConfig('device') || {}; + return device.h || window.innerHeight; } function getCoppa() { diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index a03626d4a1f..883c931824b 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -28,6 +28,8 @@ const DAYS_TO_CACHE = 7; const DAY_MS = 60 * 60 * 24 * 1000; const MISSING_CORE_CONSENT = 111; const GVLID = 95; +const ID_HOST = 'id.crwdcntrl.net'; +const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); let cookieDomain; @@ -57,12 +59,14 @@ function setProfileId(profileId) { * Get the Lotame profile id by checking cookies first and then local storage */ function getProfileId() { + let profileId; if (storage.cookiesAreEnabled()) { - return storage.getCookie(KEY_PROFILE, undefined); + profileId = storage.getCookie(KEY_PROFILE, undefined); } - if (storage.hasLocalStorage()) { - return storage.getDataFromLocalStorage(KEY_PROFILE, undefined); + if (!profileId && storage.hasLocalStorage()) { + profileId = storage.getDataFromLocalStorage(KEY_PROFILE, undefined); } + return profileId; } /** @@ -78,10 +82,11 @@ function getFromStorage(key) { const storedValueExp = storage.getDataFromLocalStorage( `${key}_exp`, undefined ); - if (storedValueExp === '') { + + if (storedValueExp === '' || storedValueExp === null) { value = storage.getDataFromLocalStorage(key, undefined); } else if (storedValueExp) { - if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { + if ((new Date(parseInt(storedValueExp, 10))).getTime() - Date.now() > 0) { value = storage.getDataFromLocalStorage(key, undefined); } } @@ -248,6 +253,13 @@ export const lotamePanoramaIdSubmodule = { usPrivacy = getFromStorage('us_privacy'); } + const getRequestHost = function() { + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + return ID_HOST_COOKIELESS; + } + return ID_HOST; + } + const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { @@ -284,7 +296,7 @@ export const lotamePanoramaIdSubmodule = { const url = buildUrl({ protocol: 'https', - host: `id.crwdcntrl.net`, + host: getRequestHost(), pathname: '/id', search: isEmpty(queryParams) ? undefined : queryParams, }); diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js new file mode 100644 index 00000000000..c6911897e84 --- /dev/null +++ b/modules/magniteAnalyticsAdapter.js @@ -0,0 +1,915 @@ +import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, deepSetValue, deepClone, logInfo, isGptPubadsDefined } from '../src/utils.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const RUBICON_GVL_ID = 52; +export const storage = getStorageManager({ gvlid: RUBICON_GVL_ID, moduleName: 'magnite' }); +const COOKIE_NAME = 'mgniSession'; +const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins +const END_EXPIRE_TIME = 21600000; // 6 hours +const MODULE_NAME = 'Magnite Analytics'; +const BID_REJECTED_IPF = 'rejected-ipf'; + +// List of known rubicon aliases +// This gets updated on auction init to account for any custom aliases present +let rubiconAliases = ['rubicon']; + +const pbsErrorMap = { + 1: 'timeout-error', + 2: 'input-error', + 3: 'connect-error', + 4: 'request-error', + 999: 'generic-error' +} + +let prebidGlobal = getGlobal(); +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_TIMEOUT, + BID_WON, + BILLABLE_EVENT + }, + STATUS: { + GOOD, + NO_BID + }, + BID_STATUS: { + BID_REJECTED + } +} = CONSTANTS; + +// The saved state of rubicon specific setConfig controls +export let rubiConf; +// Saving state of all our data we want +let cache; +const resetConfs = () => { + cache = { + auctions: {}, + auctionOrder: [], + timeouts: {}, + billing: {}, + pendingEvents: {}, + eventPending: false, + elementIdMap: {}, + sessionData: {} + } + rubiConf = { + pvid: generateUUID().slice(0, 8), + analyticsEventDelay: 500, + analyticsBatchTimeout: 5000, + analyticsProcessDelay: 1, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + } + } +} +resetConfs(); + +config.getConfig('rubicon', config => { + mergeDeep(rubiConf, config.rubicon); + if (deepAccess(config, 'rubicon.updatePageView') === true) { + rubiConf.pvid = generateUUID().slice(0, 8) + } +}); + +// pbs confs +let serverConfig; +config.getConfig('s2sConfig', ({ s2sConfig }) => { + serverConfig = s2sConfig; +}); + +const DEFAULT_INTEGRATION = 'pbjs'; + +const adUnitIsOnlyInstream = adUnit => { + return adUnit.mediaTypes && Object.keys(adUnit.mediaTypes).length === 1 && deepAccess(adUnit, 'mediaTypes.video.context') === 'instream'; +} + +const sendPendingEvents = () => { + cache.pendingEvents.trigger = `batched-${Object.keys(cache.pendingEvents).sort().join('-')}`; + sendEvent(cache.pendingEvents); + cache.pendingEvents = {}; + cache.eventPending = false; +} + +const addEventToQueue = (event, auctionId, eventName) => { + // If it's auction has not left yet, add it there + if (cache.auctions[auctionId] && !cache.auctions[auctionId].sent) { + cache.auctions[auctionId].pendingEvents = mergeDeep(cache.auctions[auctionId].pendingEvents, event); + } else if (rubiConf.analyticsEventDelay > 0) { + // else if we are trying to batch stuff up, add it to pending events to be fired + cache.pendingEvents = mergeDeep(cache.pendingEvents, event); + + // If no event is pending yet, start a timer for them to be sent and attempted to be gathered together + if (!cache.eventPending) { + setTimeout(sendPendingEvents, rubiConf.analyticsEventDelay); + cache.eventPending = true; + } + } else { + // else - send it solo + event.trigger = `solo-${eventName}`; + sendEvent(event); + } +} + +const sendEvent = payload => { + const event = { + ...getTopLevelDetails(), + ...payload + } + ajax( + endpoint, + null, + JSON.stringify(event), + { + contentType: 'application/json' + } + ); +} + +const sendAuctionEvent = (auctionId, trigger) => { + let auctionCache = cache.auctions[auctionId]; + const auctionEvent = formatAuction(auctionCache.auction); + + auctionCache.sent = true; + sendEvent({ + auctions: [auctionEvent], + ...(auctionCache.pendingEvents || {}), // if any pending events were attached + trigger + }); +} + +const formatAuction = auction => { + const auctionEvent = deepClone(auction); + + auctionEvent.samplingFactor = 1; + + // We stored adUnits and bids as objects for quick lookups, now they are mapped into arrays for PBA + auctionEvent.adUnits = Object.entries(auctionEvent.adUnits).map(([tid, adUnit]) => { + adUnit.bids = Object.entries(adUnit.bids).map(([bidId, bid]) => { + // determine adUnit.status from its bid statuses. Use priority below to determine, higher index is better + let statusPriority = ['error', 'no-bid', 'success']; + if (statusPriority.indexOf(bid.status) > statusPriority.indexOf(adUnit.status)) { + adUnit.status = bid.status; + } + + // If PBS told us to overwrite the bid ID, do so + if (bid.pbsBidId) { + bid.oldBidId = bid.bidId; + bid.bidId = bid.pbsBidId; + delete bid.pbsBidId; + } + return bid; + }); + return adUnit; + }); + return auctionEvent; +} + +const isBillingEventValid = event => { + // vendor is whitelisted + const isWhitelistedVendor = rubiConf.dmBilling.vendors.includes(event.vendor); + // event is not duplicated + const isNotDuplicate = typeof deepAccess(cache.billing, `${event.vendor}.${event.billingId}`) !== 'boolean'; + // billingId is defined and a string + return typeof event.billingId === 'string' && isWhitelistedVendor && isNotDuplicate; +} + +const formatBillingEvent = event => { + let billingEvent = deepClone(event); + // Pass along type if is string and not empty else general + billingEvent.type = (typeof event.type === 'string' && event.type) || 'general'; + billingEvent.accountId = accountId; + // mark as sent + deepSetValue(cache.billing, `${event.vendor}.${event.billingId}`, true); + return billingEvent; +} + +const getBidPrice = bid => { + // get the cpm from bidResponse + let cpm; + let currency; + if (bid.status === BID_REJECTED && typeof deepAccess(bid, 'floorData.cpmAfterAdjustments') === 'number') { + // if bid was rejected and bid.floorData.cpmAfterAdjustments use it + cpm = bid.floorData.cpmAfterAdjustments; + currency = bid.floorData.floorCurrency; + } else if (typeof bid.currency === 'string' && bid.currency.toUpperCase() === 'USD') { + // bid is in USD use it + return Number(bid.cpm); + } else { + // else grab cpm + cpm = bid.cpm; + currency = bid.currency; + } + // if after this it is still going and is USD then return it. + if (currency === 'USD') { + return Number(cpm); + } + // otherwise we convert and return + try { + return Number(prebidGlobal.convertCurrency(cpm, currency, 'USD')); + } catch (err) { + logWarn(`${MODULE_NAME}: Could not determine the bidPriceUSD of the bid `, bid); + bid.conversionError = true; + bid.ogCurrency = currency; + bid.ogPrice = cpm; + return 0; + } +} + +export const parseBidResponse = (bid, previousBidResponse) => { + // The current bidResponse for this matching requestId/bidRequestId + let responsePrice = getBidPrice(bid) + // we need to compare it with the previous one (if there was one) log highest only + // THIS WILL CHANGE WITH ALLOWING MULTIBID BETTER + if (previousBidResponse && previousBidResponse.bidPriceUSD > responsePrice) { + return previousBidResponse; + } + + return pick(bid, [ + 'bidPriceUSD', () => responsePrice, + 'dealId', dealId => dealId || undefined, + 'mediaType', + 'dimensions', () => { + const width = bid.width || bid.playerWidth; + const height = bid.height || bid.playerHeight; + return (width && height) ? { width, height } : undefined; + }, + 'floorValue', () => deepAccess(bid, 'floorData.floorValue'), + 'floorRuleValue', () => deepAccess(bid, 'floorData.floorRuleValue'), + 'floorRule', () => debugTurnedOn() ? deepAccess(bid, 'floorData.floorRule') : undefined, + 'adomains', () => { + const adomains = deepAccess(bid, 'meta.advertiserDomains'); + const validAdomains = Array.isArray(adomains) && adomains.filter(domain => typeof domain === 'string'); + return validAdomains && validAdomains.length > 0 ? validAdomains.slice(0, 10) : undefined + }, + 'networkId', () => { + const networkId = deepAccess(bid, 'meta.networkId'); + // if not a valid after this, set to undefined so it gets filtered out + return (networkId && networkId.toString()) || undefined; + }, + 'conversionError', conversionError => conversionError === true || undefined, // only pass if exactly true + 'ogCurrency', + 'ogPrice', + ]); +} + +const addFloorData = floorData => { + if (floorData.location === 'noData') { + return pick(floorData, [ + 'location', + 'fetchStatus', + 'floorProvider as provider' + ]); + } else { + return pick(floorData, [ + 'location', + 'modelVersion as modelName', + 'modelWeight', + 'modelTimestamp', + 'skipped', + 'enforcement', () => deepAccess(floorData, 'enforcements.enforceJS'), + 'dealsEnforced', () => deepAccess(floorData, 'enforcements.floorDeals'), + 'skipRate', + 'fetchStatus', + 'floorMin', + 'floorProvider as provider' + ]); + } +} + +let pageReferer; + +const getTopLevelDetails = () => { + let payload = { + channel: 'web', + integration: rubiConf.int_type || DEFAULT_INTEGRATION, + referrerUri: pageReferer, + version: '$prebid.version$', + referrerHostname: magniteAdapter.referrerHostname || getHostNameFromReferer(pageReferer), + timestamps: { + timeSincePageLoad: performance.now(), + eventTime: Date.now(), + prebidLoaded: magniteAdapter.MODULE_INITIALIZED_TIME + } + } + + // Add DM wrapper details + if (rubiConf.wrapperName) { + payload.wrapper = { + name: rubiConf.wrapperName, + family: rubiConf.wrapperFamily, + rule: rubiConf.rule_name + } + } + + if (cache.sessionData) { + // gather session info + payload.session = pick(cache.sessionData, [ + 'id', + 'pvid', + 'start', + 'expires' + ]); + // Any FPKVS set? + if (!isEmpty(cache.sessionData.fpkvs)) { + payload.fpkvs = Object.keys(cache.sessionData.fpkvs).map(key => { + return { key, value: cache.sessionData.fpkvs[key] }; + }); + } + } + return payload; +} + +export const getHostNameFromReferer = referer => { + try { + magniteAdapter.referrerHostname = parseUrl(referer, { noDecodeWholeURL: true }).hostname; + } catch (e) { + logError(`${MODULE_NAME}: Unable to parse hostname from supplied url: `, referer, e); + magniteAdapter.referrerHostname = ''; + } + return magniteAdapter.referrerHostname +}; + +const getRpaCookie = () => { + let encodedCookie = storage.getDataFromLocalStorage(COOKIE_NAME); + if (encodedCookie) { + try { + return JSON.parse(window.atob(encodedCookie)); + } catch (e) { + logError(`${MODULE_NAME}: Unable to decode ${COOKIE_NAME} value: `, e); + } + } + return {}; +} + +const setRpaCookie = (decodedCookie) => { + try { + storage.setDataInLocalStorage(COOKIE_NAME, window.btoa(JSON.stringify(decodedCookie))); + } catch (e) { + logError(`${MODULE_NAME}: Unable to encode ${COOKIE_NAME} value: `, e); + } +} + +const updateRpaCookie = () => { + const currentTime = Date.now(); + let decodedRpaCookie = getRpaCookie(); + if ( + !Object.keys(decodedRpaCookie).length || + (currentTime - decodedRpaCookie.lastSeen) > LAST_SEEN_EXPIRE_TIME || + decodedRpaCookie.expires < currentTime + ) { + decodedRpaCookie = { + id: generateUUID(), + start: currentTime, + expires: currentTime + END_EXPIRE_TIME, // six hours later, + } + } + // possible that decodedRpaCookie is undefined, and if it is, we probably are blocked by storage or some other exception + if (Object.keys(decodedRpaCookie).length) { + decodedRpaCookie.lastSeen = currentTime; + decodedRpaCookie.fpkvs = { ...decodedRpaCookie.fpkvs, ...getFpkvs() }; + decodedRpaCookie.pvid = rubiConf.pvid; + setRpaCookie(decodedRpaCookie) + } + return decodedRpaCookie; +} + +/* + Filters and converts URL Params into an object and returns only KVs that match the 'utm_KEY' format +*/ +const getUtmParams = () => { + let search; + + try { + search = parseQS(getWindowLocation().search); + } catch (e) { + search = {}; + } + + return Object.keys(search).reduce((accum, param) => { + if (param.match(/utm_/)) { + accum[param.replace(/utm_/, '')] = search[param]; + } + return accum; + }, {}); +} + +const getFpkvs = () => { + rubiConf.fpkvs = Object.assign((rubiConf.fpkvs || {}), getUtmParams()); + + // convert all values to strings + Object.keys(rubiConf.fpkvs).forEach(key => { + rubiConf.fpkvs[key] = rubiConf.fpkvs[key] + ''; + }); + + return rubiConf.fpkvs; +} + +/* + Checks the alias registry for any entries of the rubicon bid adapter. + adds to the rubiconAliases list if found +*/ +const setRubiconAliases = (aliasRegistry) => { + const otherAliases = Object.keys(aliasRegistry).filter(alias => aliasRegistry[alias] === 'rubicon'); + rubiconAliases.push(...otherAliases); +} + +const sizeToDimensions = size => { + return { + width: size.w || size[0], + height: size.h || size[1] + }; +} + +const findMatchingAdUnitFromAuctions = (matchesFunction, returnFirstMatch) => { + // finding matching adUnit / auction + let matches = {}; + + // loop through auctions in order and adunits + for (const auctionId of cache.auctionOrder) { + const auction = cache.auctions[auctionId].auction; + for (const transactionId in auction.adUnits) { + const adUnit = auction.adUnits[transactionId]; + + // check if this matches + let doesMatch; + try { + doesMatch = matchesFunction(adUnit, auction); + } catch (error) { + logWarn(`${MODULE_NAME}: Error running matches function: ${returnFirstMatch}`, error); + doesMatch = false; + } + if (doesMatch) { + matches = { adUnit, auction }; + + // we either return first match or we want last one matching so go to end + if (returnFirstMatch) return matches; + } + } + } + return matches; +} + +const getRenderingIds = bidWonData => { + // if bid caching off -> return the bidWon auction id + if (!config.getConfig('useBidCache')) { + return { + renderTransactionId: bidWonData.transactionId, + renderAuctionId: bidWonData.auctionId + }; + } + + // a rendering auction id is the LATEST auction / adunit which contains GAM ID's + const matchingFunction = (adUnit, auction) => { + // does adUnit match our bidWon and gam id's are present + const gamHasRendered = deepAccess(cache, `auctions.${auction.auctionId}.gamRenders.${adUnit.transactionId}`); + return adUnit.adUnitCode === bidWonData.adUnitCode && gamHasRendered; + } + let { adUnit, auction } = findMatchingAdUnitFromAuctions(matchingFunction, false); + // If no match was found, we will use the actual bid won auction id + return { + renderTransactionId: (adUnit && adUnit.transactionId) || bidWonData.transactionId, + renderAuctionId: (auction && auction.auctionId) || bidWonData.auctionId + } +} + +const formatBidWon = bidWonData => { + // get transaction and auction id of where this "rendered" + const { renderTransactionId, renderAuctionId } = getRenderingIds(bidWonData); + + const isCachedBid = renderTransactionId !== bidWonData.transactionId; + logInfo(`${MODULE_NAME}: Bid Won : `, { + isCachedBid, + renderAuctionId, + renderTransactionId, + sourceAuctionId: bidWonData.auctionId, + sourceTransactionId: bidWonData.transactionId, + }); + + // get the bid from the source auction id + let bid = deepAccess(cache, `auctions.${bidWonData.auctionId}.auction.adUnits.${bidWonData.transactionId}.bids.${bidWonData.requestId}`); + let adUnit = deepAccess(cache, `auctions.${bidWonData.auctionId}.auction.adUnits.${bidWonData.transactionId}`); + let bidWon = { + ...bid, + sourceAuctionId: bidWonData.auctionId, + renderAuctionId, + transactionId: bidWonData.transactionId, + sourceTransactionId: bidWonData.transactionId, + bidId: bid.pbsBidId || bidWonData.bidId || bidWonData.requestId, // if PBS had us overwrite bidId, use that as signal + renderTransactionId, + accountId, + siteId: adUnit.siteId, + zoneId: adUnit.zoneId, + mediaTypes: adUnit.mediaTypes, + adUnitCode: adUnit.adUnitCode, + isCachedBid: isCachedBid || undefined // only send if it is true (save some space) + } + delete bidWon.pbsBidId; // if pbsBidId is there delete it (no need to pass it) + return bidWon; +} + +const formatGamEvent = (slotEvent, adUnit, auction) => { + const gamEvent = pick(slotEvent, [ + // these come in as `null` from Gpt, which when stringified does not get removed + // so set explicitly to undefined when not a number + 'advertiserId', advertiserId => isNumber(advertiserId) ? advertiserId : undefined, + 'creativeId', creativeId => isNumber(slotEvent.sourceAgnosticCreativeId) ? slotEvent.sourceAgnosticCreativeId : isNumber(creativeId) ? creativeId : undefined, + 'lineItemId', lineItemId => isNumber(slotEvent.sourceAgnosticLineItemId) ? slotEvent.sourceAgnosticLineItemId : isNumber(lineItemId) ? lineItemId : undefined, + 'adSlot', () => slotEvent.slot.getAdUnitPath(), + 'isSlotEmpty', () => slotEvent.isEmpty || undefined + ]); + gamEvent.auctionId = auction.auctionId; + gamEvent.transactionId = adUnit.transactionId; + return gamEvent; +} + +const subscribeToGamSlots = () => { + window.googletag.pubads().addEventListener('slotRenderEnded', event => { + const isMatchingAdSlot = isAdUnitCodeMatchingSlot(event.slot); + + // We want to find the FIRST auction - adUnit that matches and does not have gam data yet + const matchingFunction = (adUnit, auction) => { + // first it has to match the slot + // if the code is present in the elementIdMap then we use the matched id as code here + const elementIds = cache.elementIdMap[adUnit.adUnitCode] || [adUnit.adUnitCode]; + const matchesSlot = elementIds.some(isMatchingAdSlot); + + // next it has to have NOT already been counted as gam rendered + const gamHasRendered = deepAccess(cache, `auctions.${auction.auctionId}.gamRenders.${adUnit.transactionId}`); + return matchesSlot && !gamHasRendered; + } + let { adUnit, auction } = findMatchingAdUnitFromAuctions(matchingFunction, true); + + const slotName = `${event.slot.getAdUnitPath()} - ${event.slot.getSlotElementId()}`; + + if (!adUnit || !auction) { + logInfo(`${MODULE_NAME}: Could not find matching adUnit for Gam Render: `, { + slotName + }); + return; + } + const auctionId = auction.auctionId; + + logInfo(`${MODULE_NAME}: Gam Render: `, { + slotName, + transactionId: adUnit.transactionId, + auctionId: auctionId, + adUnit: adUnit, + }); + + // if we have an adunit, then we need to make a gam event + const gamEvent = formatGamEvent(event, adUnit, auction); + + // marking that this prebid adunit has had its matching gam render found + deepSetValue(cache, `auctions.${auctionId}.gamRenders.${adUnit.transactionId}`, true); + + addEventToQueue({ gamRenders: [gamEvent] }, auctionId, 'gam'); + + // If this auction now has all gam slots rendered, fire the payload + if (!cache.auctions[auctionId].sent && Object.keys(cache.auctions[auctionId].gamRenders).every(tid => cache.auctions[auctionId].gamRenders[tid])) { + // clear the auction end timeout + clearTimeout(cache.timeouts[auctionId]); + delete cache.timeouts[auctionId]; + + // wait for bid wons a bit or send right away + if (rubiConf.analyticsEventDelay > 0) { + setTimeout(() => { + sendAuctionEvent(auctionId, 'gam-delayed'); + }, rubiConf.analyticsEventDelay); + } else { + sendAuctionEvent(auctionId, 'gam'); + } + } + }); +} + +let accountId; +let endpoint; + +let magniteAdapter = adapter({ analyticsType: 'endpoint' }); + +magniteAdapter.originEnableAnalytics = magniteAdapter.enableAnalytics; +function enableMgniAnalytics(config = {}) { + let error = false; + // endpoint + endpoint = deepAccess(config, 'options.endpoint'); + if (!endpoint) { + logError(`${MODULE_NAME}: required endpoint missing`); + error = true; + } + // accountId + accountId = Number(deepAccess(config, 'options.accountId')); + if (!accountId) { + logError(`${MODULE_NAME}: required accountId missing`); + error = true; + } + if (!error) { + magniteAdapter.originEnableAnalytics(config); + } + // listen to gam slot renders! + if (isGptPubadsDefined()) { + subscribeToGamSlots(); + } else { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => subscribeToGamSlots()); + } +}; + +const handleBidWon = args => { + const bidWon = formatBidWon(args); + addEventToQueue({ bidsWon: [bidWon] }, bidWon.renderAuctionId, 'bidWon'); +} + +magniteAdapter.enableAnalytics = enableMgniAnalytics; + +magniteAdapter.originDisableAnalytics = magniteAdapter.disableAnalytics; +magniteAdapter.disableAnalytics = function () { + // trick analytics module to register our enable back as main one + magniteAdapter._oldEnable = enableMgniAnalytics; + endpoint = undefined; + accountId = undefined; + resetConfs(); + magniteAdapter.originDisableAnalytics(); +}; + +magniteAdapter.onDataDeletionRequest = function () { + if (storage.localStorageIsEnabled()) { + storage.removeDataFromLocalStorage(COOKIE_NAME); + } else { + throw Error('Unable to access local storage, no data deleted'); + } +}; + +magniteAdapter.MODULE_INITIALIZED_TIME = Date.now(); +magniteAdapter.referrerHostname = ''; + +magniteAdapter.track = ({ eventType, args }) => { + switch (eventType) { + case AUCTION_INIT: + // Update session + cache.sessionData = storage.localStorageIsEnabled() && updateRpaCookie(); + // set the rubicon aliases + setRubiconAliases(adapterManager.aliasRegistry); + + // latest page "referer" + pageReferer = deepAccess(args, 'bidderRequests.0.refererInfo.page'); + + // set auction level data + let auctionData = pick(args, [ + 'auctionId', + 'timestamp as auctionStart', + 'timeout as clientTimeoutMillis', + ]); + auctionData.accountId = accountId; + + // Order bidders were called + auctionData.bidderOrder = args.bidderRequests.map(bidderRequest => bidderRequest.bidderCode); + + // Price Floors information + const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); + if (floorData) { + auctionData.floors = addFloorData(floorData); + } + + // GDPR info + const gdprData = deepAccess(args, 'bidderRequests.0.gdprConsent'); + if (gdprData) { + auctionData.gdpr = pick(gdprData, [ + 'gdprApplies as applies', + 'consentString', + 'apiVersion as version' + ]); + } + + // User ID Data included in auction + const userIds = Object.keys(deepAccess(args, 'bidderRequests.0.bids.0.userId', {})).map(id => { + return { provider: id, hasId: true } + }); + if (userIds.length) { + auctionData.user = { ids: userIds }; + } + + if (serverConfig) { + auctionData.serverTimeoutMillis = serverConfig.timeout; + } + + // lets us keep a map of adunit and wether it had a gam or bid won render yet, used to track when to send events + let gamRenders = {}; + // adunits saved as map of transactionIds + auctionData.adUnits = args.adUnits.reduce((adMap, adUnit) => { + let ad = pick(adUnit, [ + 'code as adUnitCode', + 'transactionId', + 'mediaTypes', mediaTypes => Object.keys(mediaTypes), + 'sizes as dimensions', sizes => (sizes || [[1, 1]]).map(sizeToDimensions), + ]); + ad.pbAdSlot = deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot'); + ad.pattern = deepAccess(adUnit, 'ortb2Imp.ext.data.aupname'); + ad.gpid = deepAccess(adUnit, 'ortb2Imp.ext.gpid'); + ad.bids = {}; + adMap[adUnit.transactionId] = ad; + gamRenders[adUnit.transactionId] = false; + + // Handle case elementId's (div Id's) are set on adUnit - PPI + const elementIds = deepAccess(adUnit, 'ortb2Imp.ext.data.elementid'); + if (elementIds) { + cache.elementIdMap[adUnit.code] = cache.elementIdMap[adUnit.code] || []; + // set it to array if set to string to be careful (should be array of strings) + const newIds = typeof elementIds === 'string' ? [elementIds] : elementIds; + newIds.forEach(id => { + if (!cache.elementIdMap[adUnit.code].includes(id)) { + cache.elementIdMap[adUnit.code].push(id); + } + }); + } + return adMap; + }, {}); + + // holding our pba data to send + cache.auctions[args.auctionId] = { + auction: auctionData, + gamRenders, + pendingEvents: {} + } + break; + case BID_REQUESTED: + args.bids.forEach(bid => { + const adUnit = deepAccess(cache, `auctions.${args.auctionId}.auction.adUnits.${bid.transactionId}`); + adUnit.bids[bid.bidId] = pick(bid, [ + 'bidder', + 'bidId', + 'source', () => bid.src === 's2s' ? 'server' : 'client', + 'status', () => 'no-bid' + ]); + // set acct site zone id on adunit + if ((!adUnit.siteId || !adUnit.zoneId) && rubiconAliases.indexOf(bid.bidder) !== -1) { + if (deepAccess(bid, 'params.accountId') == accountId) { + adUnit.accountId = parseInt(accountId); + adUnit.siteId = parseInt(deepAccess(bid, 'params.siteId')); + adUnit.zoneId = parseInt(deepAccess(bid, 'params.zoneId')); + } + } + }); + break; + case BID_RESPONSE: + const auctionEntry = deepAccess(cache, `auctions.${args.auctionId}.auction`); + const adUnit = deepAccess(auctionEntry, `adUnits.${args.transactionId}`); + let bid = adUnit.bids[args.requestId]; + + // if this came from multibid, there might now be matching bid, so check + // THIS logic will change when we support multibid per bid request + if (!bid && args.originalRequestId) { + let ogBid = adUnit.bids[args.originalRequestId]; + // create new bid + adUnit.bids[args.requestId] = { + ...ogBid, + bidId: args.requestId, + bidderDetail: args.targetingBidder + }; + bid = adUnit.bids[args.requestId]; + } + + // if we have not set enforcements yet set it (This is hidden from bidders until now so we have to get from here) + if (typeof deepAccess(auctionEntry, 'floors.enforcement') !== 'boolean' && deepAccess(args, 'floorData.enforcements')) { + auctionEntry.floors.enforcement = args.floorData.enforcements.enforceJS; + auctionEntry.floors.dealsEnforced = args.floorData.enforcements.floorDeals; + } + + // Log error if no matching bid! + if (!bid) { + logError(`${MODULE_NAME}: Could not find associated bid request for bid response with requestId: `, args.requestId); + break; + } + + // set bid status + switch (args.getStatusCode()) { + case GOOD: + bid.status = 'success'; + delete bid.error; // it's possible for this to be set by a previous timeout + break; + case NO_BID: + bid.status = args.status === BID_REJECTED ? BID_REJECTED_IPF : 'no-bid'; + delete bid.error; + break; + default: + bid.status = 'error'; + bid.error = { + code: 'request-error' + }; + } + bid.clientLatencyMillis = args.timeToRespond || Date.now() - cache.auctions[args.auctionId].auction.auctionStart; + bid.bidResponse = parseBidResponse(args, bid.bidResponse); + + // if pbs gave us back a bidId, we need to use it and update our bidId to PBA + const pbsBidId = (args.pbsBidId == 0 ? generateUUID() : args.pbsBidId) || (args.seatBidId == 0 ? generateUUID() : args.seatBidId); + if (pbsBidId) { + bid.pbsBidId = pbsBidId; + } + break; + case BIDDER_DONE: + const serverError = deepAccess(args, 'serverErrors.0'); + const serverResponseTimeMs = args.serverResponseTimeMs; + args.bids.forEach(bid => { + let cachedBid = deepAccess(cache, `auctions.${bid.auctionId}.auction.adUnits.${bid.transactionId}.bids.${bid.bidId}`); + if (typeof bid.serverResponseTimeMs !== 'undefined') { + cachedBid.serverLatencyMillis = bid.serverResponseTimeMs; + } else if (serverResponseTimeMs && bid.source === 's2s') { + cachedBid.serverLatencyMillis = serverResponseTimeMs; + } + // if PBS said we had an error, and this bid has not been processed by BID_RESPONSE YET + if (serverError && (!cachedBid.status || ['no-bid', 'error'].indexOf(cachedBid.status) !== -1)) { + cachedBid.status = 'error'; + cachedBid.error = { + code: pbsErrorMap[serverError.code] || pbsErrorMap[999], + description: serverError.message + } + } + + // set client latency if not done yet + if (!cachedBid.clientLatencyMillis) { + cachedBid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].auction.auctionStart; + } + }); + break; + case BID_WON: + // Allowing us to delay bidWon handling so it happens at right time + // we expect it to happen after gpt slotRenderEnded, but have seen it happen before when testing + // this will ensure it happens after if set + if (rubiConf.analyticsProcessDelay > 0) { + setTimeout(() => { + handleBidWon(args); + }, rubiConf.analyticsProcessDelay); + } else { + handleBidWon(args); + } + break; + case AUCTION_END: + let auctionCache = cache.auctions[args.auctionId]; + // if for some reason the auction did not do its normal thing, this could be undefied so bail + if (!auctionCache) { + break; + } + // Set this auction as being done + auctionCache.auction.auctionEnd = args.auctionEnd; + + // keeping order of auctions and if the payload has been sent or not + cache.auctionOrder.push(args.auctionId); + + const isOnlyInstreamAuction = args.adUnits && args.adUnits.every(adUnit => adUnitIsOnlyInstream(adUnit)); + + // if we are not waiting OR it is instream only auction + if (isOnlyInstreamAuction || rubiConf.analyticsBatchTimeout === 0) { + sendAuctionEvent(args.auctionId, 'solo-auction'); + } else { + // start timer to send batched payload just in case we don't hear any BID_WON events + cache.timeouts[args.auctionId] = setTimeout(() => { + sendAuctionEvent(args.auctionId, 'auctionEnd'); + }, rubiConf.analyticsBatchTimeout); + } + break; + case BID_TIMEOUT: + args.forEach(badBid => { + let bid = deepAccess(cache, `auctions.${badBid.auctionId}.auction.adUnits.${badBid.transactionId}.bids.${badBid.bidId}`, {}); + // might be set already by bidder-done, so do not overwrite + if (bid.status !== 'error') { + bid.status = 'error'; + bid.error = { + code: 'timeout-error', + description: 'prebid.js timeout' // will help us diff if timeout was set by PBS or PBJS + }; + } + }); + break; + case BILLABLE_EVENT: + if (rubiConf.dmBilling.enabled && isBillingEventValid(args)) { + // add to the map indicating it has not been sent yet + deepSetValue(cache.billing, `${args.vendor}.${args.billingId}`, false); + const billingEvent = formatBillingEvent(args); + addEventToQueue({ billableEvents: [billingEvent] }, args.auctionId, 'billing'); + } else { + logInfo(`${MODULE_NAME}: Billing event ignored`, args); + } + break; + } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: magniteAdapter, + code: 'magnite', + gvlid: RUBICON_GVL_ID +}); + +export default magniteAdapter; diff --git a/modules/magniteAnalyticsAdapter.md b/modules/magniteAnalyticsAdapter.md new file mode 100644 index 00000000000..a9ad0f4345b --- /dev/null +++ b/modules/magniteAnalyticsAdapter.md @@ -0,0 +1,18 @@ +# Magnite Analytics Adapter + +``` +Module Name: Magnite Analytics Adapter +Module Type: Analytics Adapter +Maintainer: demand-manager-support@magnite.com +``` + +## How to configure? +``` +pbjs.enableAnalytics({ + provider: 'magnite', + options: { + accountId: 12345, // The account id assigned to you by the Magnite Team + endpoint: 'http:localhost:9999/event' // Given by the Magnite Team + } +}); +``` diff --git a/modules/mass.js b/modules/mass.js index 6a01f06bd90..113fdce8d4f 100644 --- a/modules/mass.js +++ b/modules/mass.js @@ -78,7 +78,7 @@ export function updateRenderers() { /** * Before hook for 'addBidResponse'. */ -export const addBidResponseHook = timedBidResponseHook('mass', function addBidResponseHook(next, adUnitCode, bid, {index = auctionManager.index} = {}) { +export const addBidResponseHook = timedBidResponseHook('mass', function addBidResponseHook(next, adUnitCode, bid, reject, {index = auctionManager.index} = {}) { let renderer; for (let i = 0; i < renderers.length; i++) { if (renderers[i].match(bid)) { @@ -104,7 +104,7 @@ export const addBidResponseHook = timedBidResponseHook('mass', function addBidRe addListenerOnce(); } - next(adUnitCode, bid); + next(adUnitCode, bid, reject); }); /** diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 514c56061df..9b1b02e00a2 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -148,12 +148,12 @@ function isMobileAndTablet() { */ // function getBidFloor(bid, mediaType, sizes) { // var floor; -// var size = sizes.length === 1 ? sizes[0] : "*"; -// if (typeof bid.getFloor === "function") { -// const floorInfo = bid.getFloor({ currency: "USD", mediaType, size }); +// var size = sizes.length === 1 ? sizes[0] : '*'; +// if (typeof bid.getFloor === 'function') { +// const floorInfo = bid.getFloor({ currency: 'USD', mediaType, size }); // if ( -// typeof floorInfo === "object" && -// floorInfo.currency === "USD" && +// typeof floorInfo === 'object' && +// floorInfo.currency === 'USD' && // !isNaN(parseFloat(floorInfo.floor)) // ) { // floor = parseFloat(floorInfo.floor); @@ -255,7 +255,6 @@ function getItems(validBidRequests, bidderRequest) { // utils.deepAccess(req, 'ortb2Imp.ext.gpid') || // utils.deepAccess(req, 'ortb2Imp.ext.data.pbadslot') || // utils.deepAccess(req, 'params.placementId', 0); - // console.log("wjh getItems:", req, bidFloor, gpid); // if (mediaTypes.native) {} // banner广告类型 @@ -270,7 +269,7 @@ function getItems(validBidRequests, bidderRequest) { pos: 1, }, ext: { - // gpid: gpid, // 加入后无法返回广告 + // gpid: gpid, // 加入后无法返回广告 }, }; itemMaps[id] = { @@ -296,8 +295,11 @@ function getParam(validBidRequests, bidderRequest) { const sharedid = utils.deepAccess(validBidRequests[0], 'userId.sharedid.id') || utils.deepAccess(validBidRequests[0], 'userId.pubcid'); + const eids = validBidRequests[0].userIdAsEids || validBidRequests[0].userId; + let isMobile = isMobileAndTablet() ? 1 : 0; - let isTest = 0; + // input test status by Publisher. more frequently for test true req + let isTest = validBidRequests[0].params.test || 0; let auctionId = getProperty(bidderRequest, 'auctionId'); let items = getItems(validBidRequests, bidderRequest); @@ -317,19 +319,23 @@ function getParam(validBidRequests, bidderRequest) { cur: ['USD'], device: { connectiontype: 0, - // ip: '64.188.178.115', + // ip: '98.61.5.0', js: 1, - // language: "en", - // os: "Microsoft Windows", - // ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19043", + // language: 'en', + // os: 'Microsoft Windows', + // ua: 'Mozilla/5.0 (Linux; Android 12; SM-G970U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36', os: navigator.platform || '', ua: navigator.userAgent, language: /en/.test(navigator.language) ? 'en' : navigator.language, }, - ext: {}, + ext: { + eids, + }, user: { - id: sharedid || pubcid || getUserID(), + buyeruid: getUserID(), + id: sharedid || pubcid, }, + eids, site: { name: domain, domain: domain, @@ -419,12 +425,12 @@ export const spec = { nurl: getProperty(bid, 'nurl'), // adserverTargeting: { // granularityMultiplier: 0.1, - // priceGranularity: "pbHg", - // pbMg: "0.01", + // priceGranularity: 'pbHg', + // pbMg: '0.01', // }, - // pbMg: "0.01", + // pbMg: '0.01', // granularityMultiplier: 0.1, - // priceGranularity: "pbHg", + // priceGranularity: 'pbHg', }; bidResponses.push(bidResponse); } diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index b7abe5f56cf..d2d3d2bf888 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -40,6 +40,7 @@ const MEDIANET_BIDDER_CODE = 'medianet'; const PREBID_VERSION = $$PREBID_GLOBAL$$.version; const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; +const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; const BID_SUCCESS = 1; const BID_NOBID = 2; const BID_TIMEOUT = 3; @@ -51,7 +52,7 @@ const CONFIG_PASS = 1; const CONFIG_ERROR = 3; const VALID_URL_KEY = ['canonical_url', 'og_url', 'twitter_url']; -const DEFAULT_URL_KEY = 'page'; +const DEFAULT_URL_KEY = 'topmostLocation'; const LOG_TYPE = { APPR: 'APPR', @@ -62,6 +63,7 @@ let auctions = {}; let config; let pageDetails; let logsQueue = []; +let errorQueue = []; class ErrorLogger { constructor(event, additionalData) { @@ -78,6 +80,7 @@ class ErrorLogger { send() { let url = EVENT_PIXEL_URL + '?' + formatQS(this); + errorQueue.push(url); triggerPixel(url); } } @@ -160,7 +163,7 @@ class Configure { init() { // Forces Logging % to 100% - let urlObj = URL.parseUrl(pageDetails.page); + let urlObj = URL.parseUrl(pageDetails.topmostLocation); if (deepAccess(urlObj, 'search.medianet_test') || urlObj.hostname === 'localhost') { this.loggingPercent = 100; this.ajaxState = CONFIG_PASS; @@ -194,17 +197,10 @@ class PageDetail { this.canonical_url = refererInfo.canonicalUrl; this.og_url = ogUrl; this.twitter_url = twitterUrl; + this.topmostLocation = refererInfo.topmostLocation; this.screen = this._getWindowSize(); } - _getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return document.referrer; - } - } - _getWindowSize() { let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || -1; let h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || -1; @@ -235,7 +231,7 @@ class PageDetail { getLoggingData() { return { - requrl: this[config.urlToConsume] || this.page, + requrl: this[config.urlToConsume] || this.topmostLocation, dn: this.domain, ref: this.referrer, screen: this.screen @@ -402,10 +398,6 @@ class Auction { .map((bid) => bid.getLoggingData()); } - getWinnerAdslotBid(adslot) { - return this.getAdslotBids(adslot).filter((bid) => bid.winner); - } - _mergeFieldsToLog(objParams) { let logParams = []; let value; @@ -641,17 +633,22 @@ function setTargetingHandler(params) { } function bidWonHandler(bid) { - const { requestId, auctionId, adUnitCode } = bid; + const { auctionId, adUnitCode, adId } = bid; if (!(auctions[auctionId] instanceof Auction)) { return; } - let bidObj = auctions[auctionId].findBid('bidId', requestId); + let bidObj = auctions[auctionId].findBid('adId', adId); if (!(bidObj instanceof Bid)) { + new ErrorLogger(ERROR_WINNING_BID_ABSENT, { + adId: adId, + acid: auctionId, + adUnitCode, + }).send(); return; } auctions[auctionId].bidWonTime = Date.now(); bidObj.winner = 1; - sendEvent(auctionId, adUnitCode, LOG_TYPE.RA); + sendEvent(auctionId, adUnitCode, LOG_TYPE.RA, bidObj.adId); } function isSampled() { @@ -662,12 +659,12 @@ function isValidAuctionAdSlot(acid, adtag) { return (auctions[acid] instanceof Auction) && (auctions[acid].adSlots[adtag] instanceof AdSlot); } -function sendEvent(id, adunit, logType) { +function sendEvent(id, adunit, logType, adId) { if (!isValidAuctionAdSlot(id, adunit)) { return; } if (logType === LOG_TYPE.RA) { - fireAuctionLog(id, adunit, logType); + fireAuctionLog(id, adunit, logType, adId); } else { fireApPrLog(id, adunit, logType) } @@ -688,7 +685,7 @@ function getCommonLoggingData(acid, adtag) { return Object.assign(commonParams, adunitParams, auctionParams); } -function fireAuctionLog(acid, adtag, logType) { +function fireAuctionLog(acid, adtag, logType, adId) { let commonParams = getCommonLoggingData(acid, adtag); commonParams.lgtp = logType; let targeting = deepAccess(commonParams, 'targ'); @@ -699,7 +696,10 @@ function fireAuctionLog(acid, adtag, logType) { let bidParams; if (logType === LOG_TYPE.RA) { - bidParams = auctions[acid].getWinnerAdslotBid(adtag); + const winningBidObj = auctions[acid].findBid('adId', adId); + if (!winningBidObj) return; + const winLogData = winningBidObj.getLoggingData(); + bidParams = [winLogData]; commonParams.lper = 1; } else { bidParams = auctions[acid].getAdslotBids(adtag).map(({winner, ...restParams}) => restParams); @@ -767,8 +767,12 @@ let medianetAnalytics = Object.assign(adapter({URL, analyticsType}), { getlogsQueue() { return logsQueue; }, + getErrorQueue() { + return errorQueue; + }, clearlogsQueue() { logsQueue = []; + errorQueue = []; auctions = {}; }, track({ eventType, args }) { diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index d19862d2f64..01652a3fac0 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -47,6 +47,10 @@ mnData.urlData = { isTop: refererInfo.reachedTop }; +const aliases = [ + { code: 'aax', gvlid: 720 }, +]; + $$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; function getTopWindowReferrer() { @@ -418,7 +422,7 @@ export const spec = { code: BIDDER_CODE, gvlid: 142, - + aliases, supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js index 3e01d0e5631..5a159b39081 100644 --- a/modules/medianetRtdProvider.js +++ b/modules/medianetRtdProvider.js @@ -59,14 +59,14 @@ function onAuctionInitEvent(auctionInit) { }, SOURCE)); } -function getTargetingData(adUnitCode) { - const adUnits = getAdUnits(undefined, adUnitCode); +function getTargetingData(adUnitCodes, config, consent, auction) { + const adUnits = getAdUnits(auction.adUnits, adUnitCodes); let targetingData = {}; if (window.mnjs.loaded && isFn(window.mnjs.getTargetingData)) { - targetingData = window.mnjs.getTargetingData(adUnitCode, adUnits, SOURCE) || {}; + targetingData = window.mnjs.getTargetingData(adUnitCodes, adUnits, SOURCE) || {}; } const targeting = {}; - adUnitCode.forEach(adUnitCode => { + adUnitCodes.forEach(adUnitCode => { targeting[adUnitCode] = targeting[adUnitCode] || {}; targetingData[adUnitCode] = targetingData[adUnitCode] || {}; targeting[adUnitCode] = { diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index c431fe2059e..819ff280e35 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -3,6 +3,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {Renderer} from '../src/Renderer.js'; const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' @@ -10,6 +11,8 @@ const BIDDER_URL_TEST = 'https://bidder-test.mediasquare.fr/' const BIDDER_ENDPOINT_AUCTION = 'msq_prebid'; const BIDDER_ENDPOINT_WINNING = 'winning'; +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + export const spec = { code: BIDDER_CODE, gvlid: 791, @@ -40,11 +43,15 @@ export const spec = { const test = config.getConfig('debug') ? 1 : 0; let adunitValue = null; Object.keys(validBidRequests).forEach(key => { + floor = {}; adunitValue = validBidRequests[key]; if (typeof adunitValue.getFloor === 'function') { - floor = adunitValue.getFloor({currency: 'EUR', mediaType: '*', size: '*'}); - } else { - floor = {}; + if (Array.isArray(adunitValue.sizes)) { + adunitValue.sizes.forEach(value => { + let tmpFloor = adunitValue.getFloor({currency: 'USD', mediaType: '*', size: value}); + if (tmpFloor != {}) { floor[value.join('x')] = tmpFloor; } + }); + } } codes.push({ owner: adunitValue.params.owner, @@ -132,6 +139,7 @@ export const spec = { if ('url' in value['video']) { bidResponse['vastUrl'] = value['video']['url'] } if ('xml' in value['video']) { bidResponse['vastXml'] = value['video']['xml'] } bidResponse['mediaType'] = 'video'; + bidResponse['renderer'] = createRenderer(value, OUTSTREAM_RENDERER_URL); } if (value.hasOwnProperty('deal_id')) { bidResponse['dealId'] = value['deal_id']; } bidResponses.push(bidResponse); @@ -182,4 +190,35 @@ export const spec = { } } + +function outstreamRender(bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: bid.vastXml + } + }); + }); +} + +function createRenderer(bid, url) { + const renderer = Renderer.install({ + id: bid.bidId, + url: url, + loaded: false, + adUnitCode: bid.adUnitCode, + targetId: bid.adUnitCode + }); + renderer.setRender(outstreamRender); + return renderer; +} + registerBidder(spec); diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 2653f157196..036effecd88 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -66,6 +66,7 @@ _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = export const spec = { VERSION: '1.5', code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE], reId: /^[1-9][0-9]*$/, NATIVE_ASSET_ID_TO_KEY_MAP: _NATIVE_ASSET_ID_TO_KEY_MAP, @@ -177,6 +178,8 @@ export const spec = { return; } + const ortb2Data = bidderRequest?.ortb2 || {}; + let request = { id: deepAccess(bidderRequest, 'bidderRequestId'), site: {domain, page}, @@ -190,7 +193,11 @@ export const spec = { w: screen.width, language: getLanguage() }, - ext: {mgid_ver: spec.VERSION, prebid_ver: '$prebid.version$'}, + ext: { + mgid_ver: spec.VERSION, + prebid_ver: '$prebid.version$', + ...ortb2Data + }, imp }; if (bidderRequest && bidderRequest.gdprConsent) { diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js new file mode 100644 index 00000000000..f30f14ea528 --- /dev/null +++ b/modules/mgidRtdProvider.js @@ -0,0 +1,190 @@ +import { submodule } from '../src/hook.js'; +import {ajax} from '../src/ajax.js'; +import {deepAccess, logError, logInfo, mergeDeep} from '../src/utils.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'mgid'; +const MGID_RTD_API_URL = 'https://servicer.mgid.com/sda'; +const MGUID_LOCAL_STORAGE_KEY = 'mguid'; +const ORTB2_NAME = 'www.mgid.com' + +const GVLID = 358; +/** @type {?Object} */ +export const storage = getStorageManager({ + gvlid: GVLID, + moduleName: SUBMODULE_NAME +}); + +function init(moduleConfig) { + if (!moduleConfig?.params?.clientSiteId) { + logError('Mgid clientSiteId is not set!'); + return false; + } + return true; +} + +function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + let mguid; + try { + mguid = storage.getDataFromLocalStorage(MGUID_LOCAL_STORAGE_KEY); + } catch (e) { + logInfo(`Can't get mguid from localstorage`); + } + + const params = [ + { + name: 'gdprApplies', + data: typeof userConsent?.gdpr?.gdprApplies !== 'undefined' ? userConsent?.gdpr?.gdprApplies + '' : undefined, + }, + { + name: 'consentData', + data: userConsent?.gdpr?.consentString, + }, + { + name: 'uspString', + data: userConsent?.usp, + }, + { + name: 'cxurl', + data: encodeURIComponent(getContextUrl()), + }, + { + name: 'muid', + data: mguid, + }, + { + name: 'clientSiteId', + data: moduleConfig?.params?.clientSiteId, + }, + { + name: 'cxlang', + data: deepAccess(reqBidsConfigObj.ortb2Fragments.global, 'site.content.language'), + }, + ]; + + const url = MGID_RTD_API_URL + '?' + params.filter((p) => p.data).map((p) => p.name + '=' + p.data).join('&'); + + let isDone = false; + + ajax(url, { + success: (response, req) => { + if (req.status === 200) { + try { + const data = JSON.parse(response); + const ortb2 = reqBidsConfigObj?.ortb2Fragments?.global || {}; + + mergeDeep(ortb2, getDataForMerge(data)); + + if (data?.muid) { + try { + mguid = storage.setDataInLocalStorage(MGUID_LOCAL_STORAGE_KEY, data.muid); + } catch (e) { + logInfo(`Can't set mguid to localstorage`); + } + } + + onDone(); + isDone = true; + } catch (e) { + onDone(); + isDone = true; + + logError('Unable to parse Mgid RTD data', e); + } + } else { + onDone(); + isDone = true; + + logError('Mgid RTD wrong response status'); + } + }, + error: () => { + onDone(); + isDone = true; + + logError('Unable to get Mgid RTD data'); + } + }, + null, { + method: 'GET', + withCredentials: false, + }); + + setTimeout(function () { + if (!isDone) { + onDone(); + logInfo('Mgid RTD timeout'); + isDone = true; + } + }, moduleConfig.params.timeout || 1000); +} + +function getContextUrl() { + const refererInfo = getRefererInfo(); + + let resultUrl = refererInfo.canonicalUrl || refererInfo.topmostLocation; + + const metaElements = document.getElementsByTagName('meta'); + for (let i = 0; i < metaElements.length; i++) { + if (metaElements[i].getAttribute('property') === 'og:url') { + resultUrl = metaElements[i].content; + } + } + + return resultUrl; +} + +function getDataForMerge(responseData) { + let siteData = { + name: ORTB2_NAME + }; + let userData = { + name: ORTB2_NAME + }; + + if (responseData.siteSegments) { + siteData.segment = responseData.siteSegments.map((segmentId) => ({ id: segmentId })); + } + if (responseData.siteSegtax) { + siteData.ext = { + segtax: responseData.siteSegtax + } + } + + if (responseData.userSegments) { + userData.segment = responseData.userSegments.map((segmentId) => ({ id: segmentId })); + } + if (responseData.userSegtax) { + userData.ext = { + segtax: responseData.userSegtax + } + } + + let result = {}; + if (siteData.segment || siteData.ext) { + result.site = { + content: { + data: [siteData], + } + } + } + + if (userData.segment || userData.ext) { + result.user = { + data: [userData], + } + } + + return result; +} + +/** @type {RtdSubmodule} */ +export const mgidSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: getBidRequestData, +}; + +submodule(MODULE_NAME, mgidSubmodule); diff --git a/modules/mgidRtdProvider.md b/modules/mgidRtdProvider.md new file mode 100644 index 00000000000..58d4564e14e --- /dev/null +++ b/modules/mgidRtdProvider.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: Mgid RTD Provider +Module Type: RTD Provider +Maintainer: prebid@mgid.com +``` + +# Description + +Mgid RTD module allows you to enrich bid data with contextual and audience signals, based on IAB taxonomies. + +## Configuration + +This module is configured as part of the `realTimeData.dataProviders` object. + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|------------|----------|----------------------------------------|---------------|----------| +| `name ` | required | Real time data module name | `'mgid'` | `string` | +| `params` | required | | | `Object` | +| `params.clientSiteId` | required | The client site id provided by Mgid. | `'123456'` | `string` | +| `params.timeout` | optional | Maximum amount of milliseconds allowed for module to finish working | `1000` | `number` | + +#### Example + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'mgid', + params: { + clientSiteId: '123456' + } + }] + } +}); +``` + +## Integration +To install the module, follow these instructions: + +#### Step 1: Prepare the base Prebid file + +- Option 1: Use Prebid [Download](/download.html) page to build the prebid package. Ensure that you do check *Mgid Realtime Module* module + +- Option 2: From the command line, run `gulp build --modules=mgidRtdProvider,...` + +#### Step 2: Set configuration + +Enable Mgid Real Time Module using `pbjs.setConfig`. Example is provided in Configuration section. diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index 1b31000df43..13aea0f3f77 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -1,4 +1,5 @@ -import { deepAccess, isEmpty, isStr } from '../src/utils.js'; +import { deepAccess, isArray, isEmpty, isStr } from '../src/utils.js'; +import { find } from '../src/polyfill.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -24,15 +25,16 @@ const NATIVE_CODE = 2; const VIDEO_CODE = 4; const AUDIENCE_IDS = [ - {type: 6, bidKey: 'userId.imuid'}, - {type: 8, bidKey: 'userId.id5id.uid'}, - {type: 9, bidKey: 'userId.tdid'}, - {type: 10, bidKey: 'userId.novatiq.snowflake'}, - {type: 11, bidKey: 'userId.parrableId.eid'}, - {type: 12, bidKey: 'userId.dacId.id'}, - {type: 13, bidKey: 'userId.idl_env'}, - {type: 14, bidKey: 'userId.criteoId'}, - {type: 15, bidKey: 'userId.pubcid'} + {type: 6, bidKey: 'userId.imuid', source: 'intimatemerger.com'}, + {type: 8, bidKey: 'userId.id5id.uid', source: 'id5-sync.com'}, + {type: 9, bidKey: 'userId.tdid', source: 'adserver.org'}, + {type: 10, bidKey: 'userId.novatiq.snowflake', source: 'novatiq.com'}, + {type: 11, bidKey: 'userId.parrableId.eid', source: 'parrable.com'}, + {type: 12, bidKey: 'userId.dacId.id', source: 'dac.co.jp'}, + {type: 13, bidKey: 'userId.idl_env', source: 'liveramp.com'}, + {type: 14, bidKey: 'userId.criteoId', source: 'criteo.com'}, + {type: 15, bidKey: 'userId.pubcid', source: 'pubcid.org'}, + {type: 17, bidKey: 'userId.uid2.id', source: 'uidapi.com'} ]; function createCBT() { @@ -100,10 +102,19 @@ export const spec = { } const aidsParams = [] + const userIdAsEids = bid.userIdAsEids; AUDIENCE_IDS.forEach((audienceId) => { const bidAudienceId = deepAccess(bid, audienceId.bidKey); if (!isEmpty(bidAudienceId) && isStr(bidAudienceId)) { - aidsParams.push({ type: audienceId.type, id: bidAudienceId }); + const aidParam = { type: audienceId.type, id: bidAudienceId }; + // Set ext + if (isArray(userIdAsEids)) { + const targetEid = find(userIdAsEids, (eid) => eid.source === audienceId.source) || {}; + if (!isEmpty(deepAccess(targetEid, 'uids.0.ext'))) { + aidParam.ext = targetEid.uids[0].ext; + } + } + aidsParams.push(aidParam); // Set Ramp ID if (audienceId.type === 13) params['idl_env'] = bidAudienceId; } diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index 70f106a745f..66b54adaf0e 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -20,7 +20,7 @@ The adapter supports Video(instream) & Banner. | Name | Scope | Type | Description | Example | ---- | ----- | ---- | ----------- | ------- -| `org` | required | String | MinuteMedia publisher Id provided by your MinuteMedia representative | "56f91cd4d3e3660002000033" +| `org` | required | String | MinuteMedia publisher Id provided by your MinuteMedia representative | "1234567890abcdef12345678" | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false @@ -43,7 +43,7 @@ var adUnits = [{ bids: [{ bidder: 'minutemedia', params: { - org: '56f91cd4d3e3660002000033', // Required + org: '1234567890abcdef12345678', // Required floorPrice: 2.00, // Optional placementId: 'video-test', // Optional testMode: false // Optional @@ -65,7 +65,7 @@ var adUnits = [{ bids: [{ bidder: 'minutemedia', params: { - org: '56f91cd4d3e3660002000033', // Required + org: '1234567890abcdef12345678', // Required floorPrice: 2.00, // Optional placementId: 'banner-test', // Optional testMode: false // Optional diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js new file mode 100644 index 00000000000..578db846289 --- /dev/null +++ b/modules/minutemediaplusBidAdapter.js @@ -0,0 +1,291 @@ +import { _each, deepAccess, parseSizesInput, parseUrl, uniques, isFn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const GVLID = 918; +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'mmplus'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +export const SUPPORTED_ID_SYSTEMS = { + 'britepoolid': 1, + 'criteoId': 1, + 'id5id': 1, + 'idl_env': 1, + 'lipb': 1, + 'netId': 1, + 'parrableId': 1, + 'pubcid': 1, + 'tdid': 1, + 'pubProvidedId': 1 +}; +const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.minutemedia-prebid.com`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { + const { params, bidId, userId, adUnitCode, schain, mediaTypes } = bid; + let { bidFloor, ext } = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes + }; + + appendUserIdsToRequestPayload(data, userId); + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + + _each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + if (SUPPORTED_ID_SYSTEMS[idSystemProviderName]) { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + requests.push(request); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const { bidId } = request.data; + const { results } = serverResponse.body; + + let output = []; + + try { + results.forEach(result => { + const { creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + meta: { + advertiserDomains: advertiserDomains || [] + } + }; + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; + const { iframeEnabled, pixelEnabled } = syncOptions; + const { gdprApplies, consentString = '' } = gdprConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.minutemedia-prebid.com/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.minutemedia-prebid.com/api/sync/image/${params}` + }); + } + return syncs; +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({ value, created }); + storage.setDataInLocalStorage(key, data); + } catch (e) { } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/minutemediaplusBidAdapter.md b/modules/minutemediaplusBidAdapter.md new file mode 100644 index 00000000000..410c00e7017 --- /dev/null +++ b/modules/minutemediaplusBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** MinuteMediaPlus Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** hb@minutemedia.com + +# Description + +Module that connects to MinuteMediaPlus's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'mmplus', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/modules/multibid/index.js b/modules/multibid/index.js index fb28be83fa8..df77a157bee 100644 --- a/modules/multibid/index.js +++ b/modules/multibid/index.js @@ -12,6 +12,7 @@ import * as events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; import {addBidderRequests} from '../../src/auction.js'; import {getHighestCpmBidsFromBidPool, sortByDealAndPriceBucketOrCpm} from '../../src/targeting.js'; +import {PBS, registerOrtbProcessor, REQUEST} from '../../src/pbjsORTB.js'; import {timedBidResponseHook} from '../../src/utils/perfMetrics.js'; const MODULE_NAME = 'multibid'; @@ -99,7 +100,7 @@ export function adjustBidderRequestsHook(fn, bidderRequests) { * @param {String} ad unit code for bid * @param {Object} bid object */ -export const addBidResponseHook = timedBidResponseHook('multibid', function addBidResponseHook(fn, adUnitCode, bid) { +export const addBidResponseHook = timedBidResponseHook('multibid', function addBidResponseHook(fn, adUnitCode, bid, reject) { let floor = deepAccess(bid, 'floorData.floorValue'); if (!config.getConfig('multibid')) resetMultiConfig(); @@ -107,7 +108,7 @@ export const addBidResponseHook = timedBidResponseHook('multibid', function addB // Else checks if multiconfig exists and bid bidderCode exists within config // Else continue with no modifications if (hasMultibid && multiConfig[bid.bidderCode] && deepAccess(bid, 'video.context') === 'adpod') { - fn.call(this, adUnitCode, bid); + fn.call(this, adUnitCode, bid, reject); } else if (hasMultibid && multiConfig[bid.bidderCode]) { // Set property multibidPrefix on bid if (multiConfig[bid.bidderCode].prefix) bid.multibidPrefix = multiConfig[bid.bidderCode].prefix; @@ -127,7 +128,7 @@ export const addBidResponseHook = timedBidResponseHook('multibid', function addB if (multiConfig[bid.bidderCode].prefix) bid.targetingBidder = multiConfig[bid.bidderCode].prefix + length; if (length === multiConfig[bid.bidderCode].maxbids) multibidUnits[adUnitCode][bid.bidderCode].maxReached = true; - fn.call(this, adUnitCode, bid); + fn.call(this, adUnitCode, bid, reject); } else { logWarn(`Filtering multibid received from bidder ${bid.bidderCode}: ` + ((multibidUnits[adUnitCode][bid.bidderCode].maxReached) ? `Maximum bid limit reached for ad unit code ${adUnitCode}` : 'Bid cpm under floors value.')); } @@ -137,10 +138,10 @@ export const addBidResponseHook = timedBidResponseHook('multibid', function addB deepSetValue(multibidUnits, [adUnitCode, bid.bidderCode], {ads: [bid]}); if (multibidUnits[adUnitCode][bid.bidderCode].ads.length === multiConfig[bid.bidderCode].maxbids) multibidUnits[adUnitCode][bid.bidderCode].maxReached = true; - fn.call(this, adUnitCode, bid); + fn.call(this, adUnitCode, bid, reject); } } else { - fn.call(this, adUnitCode, bid); + fn.call(this, adUnitCode, bid, reject); } }); @@ -236,3 +237,14 @@ function init() { } init(); + +export function setOrtbExtPrebidMultibid(ortbRequest) { + const multibid = config.getConfig('multibid'); + if (multibid) { + deepSetValue(ortbRequest, 'ext.prebid.multibid', multibid.map(o => + Object.fromEntries(Object.entries(o).map(([k, v]) => [k.toLowerCase(), v]))) + ) + } +} + +registerOrtbProcessor({type: REQUEST, name: 'extPrebidMultibid', fn: setOrtbExtPrebidMultibid, dialects: [PBS]}); diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 7eb37ea8b82..a92168492d0 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -140,13 +140,9 @@ export const spec = { const floorPriceData = {} let placementId, pageUrl validBidRequests.forEach((bidRequest) => { - pageUrl = deepAccess( - bidRequest, - 'params.url', - ) - if (pageUrl == undefined || pageUrl === '') { - pageUrl = bidderRequest.refererInfo.location - } + pageUrl = + getPageUrlFromBidRequest(bidRequest) || + bidderRequest.refererInfo.location placementId = deepAccess(bidRequest, 'params.placementId') @@ -380,10 +376,12 @@ export const spec = { return syncs } - body = - typeof response.body === 'string' - ? JSON.parse(response.body) - : response.body + try { + body = + typeof response.body === 'string' + ? JSON.parse(response.body) + : response.body + } catch (err) { return } // Make sure we have valid content if (!body || !body.seatbid || body.seatbid.length === 0) return @@ -465,7 +463,7 @@ export function parseFloorPriceData(bidRequest) { // Setup price floor data per media type let mediaTypeData = bidMediaTypes[mediaType] let mediaTypeFloorPriceData = {} - let mediaTypeSizes = mediaTypeData.sizes || mediaTypeData.playerSize || []; + let mediaTypeSizes = mediaTypeData.sizes || mediaTypeData.playerSize || [] // Step through each size of the media type so we can get floor data for each size per media type mediaTypeSizes.forEach((size) => { // Get floor price data per the getFloor method and respective media type / size combination @@ -634,3 +632,37 @@ function appendFilterData(filter, filterData) { filterData.forEach((ad) => filter.add(ad)) } } + +export function getPageUrlFromBidRequest(bidRequest) { + let paramPageUrl = deepAccess(bidRequest, 'params.url') + + if (paramPageUrl == undefined) return + + if (hasProtocol(paramPageUrl)) return paramPageUrl + + paramPageUrl = addProtocol(paramPageUrl) + + try { + const url = new URL(paramPageUrl) + return url.href + } catch (err) {} +} + +export function hasProtocol(url) { + const protocolRegexp = /^http[s]?\:/ + return protocolRegexp.test(url) +} + +export function addProtocol(url) { + if (hasProtocol(url)) { + return url + } + + let protocolPrefix = 'https:' + + if (url.indexOf('//') !== 0) { + protocolPrefix += '//' + } + + return `${protocolPrefix}${url}` +} diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js new file mode 100644 index 00000000000..a2549a243d1 --- /dev/null +++ b/modules/neuwoRtdProvider.js @@ -0,0 +1,171 @@ +import { deepAccess, deepSetValue, generateUUID, logError, logInfo, mergeDeep } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +export const DATA_PROVIDER = 'neuwo.ai'; +const SEGTAX_IAB = 6 // IAB - Content Taxonomy version 2 +const CATTAX_IAB = 6 // IAB Tech Lab Content Taxonomy 2.2 +const RESPONSE_IAB_TIER_1 = 'marketing_categories.iab_tier_1' +const RESPONSE_IAB_TIER_2 = 'marketing_categories.iab_tier_2' + +function init(config = {}, userConsent = '') { + config.params = config.params || {} + // ignore module if publicToken is missing (module setup failure) + if (!config.params.publicToken) { + logError('publicToken missing', 'NeuwoRTDModule', 'config.params.publicToken') + return false; + } + return true; +} + +export function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + config.params = config.params || {}; + logInfo('NeuwoRTDModule', 'starting getBidRequestData') + + const wrappedArgUrl = encodeURIComponent(config.params.argUrl || getRefererInfo().page); + const url = 'https://m1apidev.neuwo.ai/edge/GetAiTopics?' + [ + 'token=' + config.params.publicToken, + 'lang=en', + 'url=' + wrappedArgUrl + ].join('&') + const billingId = generateUUID(); + + const success = (responseContent) => { + logInfo('NeuwoRTDModule', 'GetAiTopics: response', responseContent) + try { + const jsonContent = JSON.parse(responseContent); + if (jsonContent.marketing_categories) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { type: 'request', billingId, vendor: neuwoRtdModule.name }) + } + injectTopics(jsonContent, reqBidsConfigObj, billingId) + } catch (ex) { + logError('NeuwoRTDModule', 'Response to JSON parse error', ex) + } + callback() + } + + const error = (err) => { + logError('xhr error', null, err); + callback() + } + + ajax(url, {success, error}, null, { + // could assume Origin header is set, or + // customHeaders: { 'Origin': 'Origin' } + }) +} + +export function addFragment(base, path, addition) { + const container = {} + deepSetValue(container, path, addition) + mergeDeep(base, container) +} + +/** + * Concatenate a base array and an array within an object + * non-array bases will be arrays, non-arrays at object key will be discarded + * @param {array} base base array to add to + * @param {object} source object to get an array from + * @param {string} key dot-notated path to array within object + * @returns base + source[key] if that's an array + */ +function combineArray(base, source, key) { + if (Array.isArray(base) === false) base = [] + const addition = deepAccess(source, key, []) + if (Array.isArray(addition)) return base.concat(addition) + else return base +} + +export function injectTopics(topics, bidsConfig) { + topics = topics || {} + + // join arrays of IAB category details to single array + const combinedTiers = combineArray( + combineArray([], topics, RESPONSE_IAB_TIER_1), + topics, RESPONSE_IAB_TIER_2) + + const segment = pickSegments(combinedTiers) + // effectively gets topics.marketing_categories.iab_tier_1, topics.marketing_categories.iab_tier_2 + // used as FPD segments content + + const IABSegments = { + name: DATA_PROVIDER, + ext: { segtax: SEGTAX_IAB }, + segment + } + + addFragment(bidsConfig.ortb2Fragments.global, 'site.content.data', [IABSegments]) + + // upgrade category taxonomy to IAB 2.2, inject result to page categories + if (segment.length > 0) { + addFragment(bidsConfig.ortb2Fragments.global, 'site.cattax', CATTAX_IAB) + addFragment(bidsConfig.ortb2Fragments.global, 'site.pagecat', segment.map(s => s.id)) + } + + logInfo('NeuwoRTDModule', 'injectTopics: post-injection bidsConfig', bidsConfig) +} + +/* eslint-disable object-property-newline */ +const D_IAB_ID = { // Content Taxonomy version 2.0 final release November 2017 [sic] (Taxonomy ID Mapping, IAB versions 2.0 - 2.2) + 'IAB19-1': '603', 'IAB6-1': '193', 'IAB5-2': '133', 'IAB20-1': '665', 'IAB20-2': '656', 'IAB23-2': '454', 'IAB3-2': '102', 'IAB20-3': '672', 'IAB8-5': '211', + 'IAB8-18': '211', 'IAB7-4': '288', 'IAB7-5': '233', 'IAB17-12': '484', 'IAB19-3': '608', 'IAB21-1': '442', 'IAB9-2': '248', 'IAB15-1': '456', 'IAB9-17': '265', 'IAB20-4': '658', + 'IAB2-3': '30', 'IAB2-1': '32', 'IAB17-1': '518', 'IAB2-2': '34', 'IAB2': '1', 'IAB8-2': '215', 'IAB17-2': '545', 'IAB17-26': '547', 'IAB9-3': '249', 'IAB18-1': '553', 'IAB20-5': '674', + 'IAB15-2': '465', 'IAB3-3': '119', 'IAB16-2': '423', 'IAB9-4': '259', 'IAB9-5': '270', 'IAB18-2': '574', 'IAB17-4': '549', 'IAB7-33': '312', 'IAB1-1': '42', 'IAB17-5': '485', 'IAB23-3': '458', + 'IAB20-6': '675', 'IAB3': '53', 'IAB20-7': '676', 'IAB19-5': '633', 'IAB20-9': '677', 'IAB9-6': '250', 'IAB17-6': '499', 'IAB2-4': '25', 'IAB9-7': '271', 'IAB4-11': '125', 'IAB4-1': '126', + 'IAB4': '123', 'IAB16-3': '424', 'IAB2-5': '18', 'IAB17-7': '486', 'IAB15-3': '466', 'IAB23-5': '459', 'IAB9-9': '260', 'IAB2-22': '19', 'IAB17-8': '500', 'IAB9-10': '261', 'IAB5-5': '137', + 'IAB9-11': '262', 'IAB2-21': '3', 'IAB19-2': '610', 'IAB19-8': '600', 'IAB19-9': '601', 'IAB3-5': '121', 'IAB9-15': '264', 'IAB2-6': '8', 'IAB2-7': '9', 'IAB22-2': '474', 'IAB17-9': '491', + 'IAB2-8': '10', 'IAB20-12': '678', 'IAB17-3': '492', 'IAB19-12': '611', 'IAB14-1': '188', 'IAB6-3': '194', 'IAB7-17': '316', 'IAB19-13': '612', 'IAB8-8': '217', 'IAB9-1': '205', 'IAB19-22': '613', + 'IAB8-9': '218', 'IAB14-2': '189', 'IAB16-4': '425', 'IAB9-12': '251', 'IAB5': '132', 'IAB6-9': '190', 'IAB19-15': '623', 'IAB17-17': '496', 'IAB20-14': '659', 'IAB6': '186', 'IAB20-26': '666', + 'IAB17-10': '510', 'IAB13-4': '396', 'IAB1-3': '201', 'IAB16-1': '426', 'IAB17-11': '511', 'IAB17-13': '511', 'IAB17-32': '511', 'IAB7-1': '225', 'IAB8': '210', 'IAB8-10': '219', 'IAB9-13': '266', + 'IAB10-4': '275', 'IAB9-14': '273', 'IAB15-8': '469', 'IAB15-4': '470', 'IAB17-15': '512', 'IAB3-7': '77', 'IAB19-16': '614', 'IAB3-8': '78', 'IAB2-10': '22', 'IAB2-12': '22', 'IAB2-11': '11', + 'IAB8-12': '221', 'IAB7-35': '223', 'IAB7-38': '223', 'IAB7-24': '296', 'IAB13-5': '411', 'IAB7-25': '234', 'IAB23-6': '460', 'IAB9': '239', 'IAB7-26': '235', 'IAB10': '274', 'IAB10-1': '278', + 'IAB10-2': '279', 'IAB19-17': '634', 'IAB10-5': '280', 'IAB5-10': '145', 'IAB5-11': '146', 'IAB20-17': '667', 'IAB17-16': '497', 'IAB20-18': '668', 'IAB3-9': '55', 'IAB1-4': '440', 'IAB17-18': '514', + 'IAB17-27': '515', 'IAB10-3': '282', 'IAB19-25': '618', 'IAB17-19': '516', 'IAB13-6': '398', 'IAB10-7': '283', 'IAB12-1': '382', 'IAB19-24': '624', 'IAB6-4': '195', 'IAB23-7': '461', 'IAB9-19': '252', + 'IAB4-4': '128', 'IAB4-5': '127', 'IAB23-8': '462', 'IAB10-8': '284', 'IAB5-8': '147', 'IAB16-5': '427', 'IAB11-2': '383', 'IAB12-3': '384', 'IAB3-10': '57', 'IAB2-13': '23', 'IAB9-20': '241', + 'IAB3-1': '58', 'IAB3-11': '58', 'IAB14-4': '191', 'IAB17-20': '520', 'IAB7-31': '228', 'IAB7-37': '301', 'IAB3-12': '107', 'IAB2-14': '13', 'IAB17-25': '519', 'IAB2-15': '27', 'IAB1-5': '324', + 'IAB1-6': '338', 'IAB9-16': '243', 'IAB13-8': '412', 'IAB12-2': '385', 'IAB9-21': '253', 'IAB8-6': '222', 'IAB7-32': '229', 'IAB2-16': '14', 'IAB17-23': '521', 'IAB13-9': '413', 'IAB17-24': '501', + 'IAB9-22': '254', 'IAB15-5': '244', 'IAB6-2': '196', 'IAB6-5': '197', 'IAB6-6': '198', 'IAB2-17': '24', 'IAB13-2': '405', 'IAB13': '391', 'IAB13-7': '410', 'IAB13-12': '415', 'IAB16': '422', + 'IAB9-23': '255', 'IAB7-36': '236', 'IAB15-6': '471', 'IAB2-18': '15', 'IAB11-4': '386', 'IAB1-2': '432', 'IAB5-9': '139', 'IAB6-7': '305', 'IAB5-12': '149', 'IAB5-13': '134', 'IAB19-4': '631', + 'IAB19-19': '631', 'IAB19-20': '631', 'IAB19-32': '631', 'IAB9-24': '245', 'IAB21': '441', 'IAB21-3': '451', 'IAB23': '453', 'IAB10-9': '276', 'IAB4-9': '130', 'IAB16-6': '429', 'IAB4-6': '129', + 'IAB13-10': '416', 'IAB2-19': '28', 'IAB17-28': '525', 'IAB9-25': '272', 'IAB17-29': '527', 'IAB17-30': '227', 'IAB17-31': '530', 'IAB22-1': '481', 'IAB15': '464', 'IAB9-26': '246', 'IAB9-27': '256', + 'IAB9-28': '267', 'IAB17-33': '502', 'IAB19-35': '627', 'IAB2-20': '4', 'IAB7-39': '307', 'IAB19-30': '605', 'IAB22': '473', 'IAB17-34': '503', 'IAB17-35': '531', 'IAB7-19': '309', 'IAB7-40': '310', + 'IAB19-6': '635', 'IAB7-41': '237', 'IAB17-36': '504', 'IAB17-44': '533', 'IAB20-23': '662', 'IAB15-7': '472', 'IAB20-24': '671', 'IAB5-14': '136', 'IAB6-8': '199', 'IAB17': '483', 'IAB9-29': '263', + 'IAB2-23': '5', 'IAB13-11': '414', 'IAB4-3': '395', 'IAB18': '552', 'IAB7-42': '311', 'IAB17-37': '505', 'IAB17-38': '537', 'IAB17-39': '538', 'IAB19-26': '636', 'IAB19': '596', 'IAB1-7': '640', + 'IAB17-40': '539', 'IAB7-43': '293', 'IAB20': '653', 'IAB8-16': '212', 'IAB8-17': '213', 'IAB16-7': '430', 'IAB9-30': '680', 'IAB17-41': '541', 'IAB17-42': '542', 'IAB17-43': '506', 'IAB15-10': '390', + 'IAB19-23': '607', 'IAB19-34': '629', 'IAB14-7': '165', 'IAB7-44': '231', 'IAB7-45': '238', 'IAB9-31': '257', 'IAB5-1': '135', 'IAB7-2': '301', 'IAB18-6': '580', 'IAB7-3': '297', 'IAB23-1': '453', + 'IAB8-1': '214', 'IAB7-6': '312', 'IAB7-7': '300', 'IAB7-8': '301', 'IAB13-1': '410', 'IAB7-9': '301', 'IAB15-9': '465', 'IAB7-10': '313', 'IAB3-4': '602', 'IAB20-8': '660', 'IAB8-3': '214', + 'IAB20-10': '660', 'IAB7-11': '314', 'IAB20-11': '660', 'IAB23-4': '459', 'IAB9-8': '270', 'IAB8-4': '214', 'IAB7-12': '306', 'IAB7-13': '313', 'IAB7-14': '287', 'IAB18-5': '575', 'IAB7-15': '315', + 'IAB8-7': '214', 'IAB19-11': '616', 'IAB7-16': '289', 'IAB7-18': '301', 'IAB7-20': '290', 'IAB20-13': '659', 'IAB7-21': '313', 'IAB18-3': '579', 'IAB13-3': '52', 'IAB20-15': '659', 'IAB8-11': '214', + 'IAB7-22': '318', 'IAB20-16': '659', 'IAB7-23': '313', 'IAB7': '223', 'IAB10-6': '634', 'IAB7-27': '318', 'IAB11-1': '388', 'IAB7-29': '318', 'IAB7-30': '304', 'IAB19-18': '619', 'IAB8-13': '214', + 'IAB20-19': '659', 'IAB20-20': '657', 'IAB8-14': '214', 'IAB18-4': '565', 'IAB23-9': '459', 'IAB11': '379', 'IAB8-15': '214', 'IAB20-21': '662', 'IAB17-21': '492', 'IAB17-22': '518', 'IAB12': '379', + 'IAB23-10': '453', 'IAB7-34': '301', 'IAB4-8': '395', 'IAB26-3': '608', 'IAB20-25': '151', 'IAB20-27': '659' +} + +export function convertSegment(segment) { + if (!segment) return {} + return { + id: D_IAB_ID[segment.id || segment.ID] + } +} + +/** + * map array of objects to segments + * @param {Array[{ID: string}]} normalizable + * @returns array of IAB "segments" + */ +export function pickSegments(normalizable) { + if (Array.isArray(normalizable) === false) return [] + return normalizable.map(convertSegment) + .filter(t => t.id) +} + +export const neuwoRtdModule = { + name: 'NeuwoRTDModule', + init, + getBidRequestData +} + +submodule('realTimeData', neuwoRtdModule) diff --git a/modules/neuwoRtdProvider.md b/modules/neuwoRtdProvider.md new file mode 100644 index 00000000000..3552a018563 --- /dev/null +++ b/modules/neuwoRtdProvider.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: Neuwo Rtd Provider +Module Type: Rtd Provider +Maintainer: neuwo.ai + +# Description + +Real-time data provider for Neuwo AI. Contact neuwo.ai [https://neuwo.ai/contact-us] for more information. + +# Configuration + +```javascript + +const neuwoDataProvider = { + name: 'NeuwoRTDModule', + params: { + publicToken: '' + } +} +pbjs.setConfig({realTimeData: { dataProviders: [ neuwoDataProvider ]}}) + +``` + +# Testing + +## Add development tools if necessary + +- Install node for npm +- run in prebid.js source folder: +`npm ci` +`npm i -g gulp-cli` + +## Serve + +`gulp serve --modules=rtdModule,neuwoRtdProvider,appnexusBidAdapter` + +- in your browser, navigate to: + +`http://localhost:9999/integrationExamples/gpt/neuwoRtdProvider_example.html` diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 25ad1f43e82..6abb369522b 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,4 +1,5 @@ import { + isArray, _each, createTrackPixelHtml, deepAccess, @@ -14,6 +15,7 @@ import { import CONSTANTS from '../src/constants.json'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; import * as events from '../src/events.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -22,18 +24,22 @@ import { getRefererInfo } from '../src/refererDetection.js'; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction'; -const SYNC_ENDPOINT = 'https://statics.nextmillmedia.com/load-cookie.html?v=4'; +const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?'; +const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric'; const TIME_TO_LIVE = 360; const VIDEO_PARAMS = [ 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'protocols', 'startdelay' ]; +const sendingDataStatistic = initSendingDataStatistic(); +events.on(CONSTANTS.EVENTS.AUCTION_INIT, auctionInitHandler); + const EXPIRENCE_WURL = 20 * 60000; const wurlMap = {}; +cleanWurl(); events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); -cleanWurl(); export const spec = { code: BIDDER_CODE, @@ -135,17 +141,19 @@ export const spec = { const urlParameters = parseUrl(getWindowTop().location.href).search; const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; + const params = bid.params; requests.push({ method: 'POST', url: isTest ? TEST_ENDPOINT : ENDPOINT, data: JSON.stringify(postBody), options: { - contentType: 'application/json', + contentType: 'text/plain', withCredentials: true }, bidId, + params, auctionId, }); }); @@ -160,6 +168,7 @@ export const spec = { _each(response.seatbid, (resp) => { _each(resp.bid, (bid) => { const requestId = bidRequest.bidId; + const params = bidRequest.params; const auctionId = bidRequest.auctionId; const wurl = deepAccess(bid, 'ext.prebid.events.win'); addWurl({auctionId, requestId, wurl}); @@ -168,12 +177,13 @@ export const spec = { const bidResponse = { requestId, + params, cpm: bid.price, width: bid.w, height: bid.h, creativeId: bid.adid, currency: response.cur, - netRevenue: false, + netRevenue: true, ttl: TIME_TO_LIVE, meta: { advertiserDomains: bid.adomain || [] @@ -198,31 +208,98 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { - if (!syncOptions.iframeEnabled) { - return; - }; + const pixels = []; + + if (isArray(responses)) { + responses.forEach(response => { + if (syncOptions.pixelEnabled) { + deepAccess(response, 'body.ext.sync.image', []).forEach(imgUrl => { + pixels.push({ + type: 'image', + url: replaceUsersyncMacros(imgUrl, gdprConsent, uspConsent) + }); + }) + } - let syncurl = gdprConsent && gdprConsent.gdprApplies ? `${SYNC_ENDPOINT}&gdpr=1&gdpr_consent=${gdprConsent.consentString}` : SYNC_ENDPOINT; + if (syncOptions.iframeEnabled) { + deepAccess(response, 'body.ext.sync.iframe', []).forEach(iframeUrl => { + pixels.push({ + type: 'iframe', + url: replaceUsersyncMacros(iframeUrl, gdprConsent, uspConsent) + }); + }) + } + }) + } + + if (!pixels.length) { + let syncUrl = SYNC_ENDPOINT; + if (gdprConsent && gdprConsent.gdprApplies) syncUrl += 'gdpr=1&gdpr_consent=' + gdprConsent.consentString + '&'; + if (uspConsent) syncUrl += 'us_privacy=' + uspConsent + '&'; + if (syncOptions.iframeEnabled) pixels.push({type: 'iframe', url: syncUrl + 'type=iframe'}); + if (syncOptions.pixelEnabled) pixels.push({type: 'image', url: syncUrl + 'type=image'}); + } + return pixels; + }, - let bidders = []; - if (responses) { - _each(responses, (response) => { - if (!(response && response.body && response.body.ext && response.body.ext.responsetimemillis)) return; - _each(Object.keys(response.body.ext.responsetimemillis), b => bidders.push(b)); - }); + getUrlPixelMetric(eventName, bid) { + const bidder = bid.bidder || bid.bidderCode; + if (bidder != BIDDER_CODE) return; + + let params; + if (bid.params) { + params = Array.isArray(bid.params) ? bid.params : [bid.params]; + } else { + if (Array.isArray(bid.bids)) params = bid.bids.map(bidI => bidI.params); }; - if (bidders.length) { - syncurl += `&bidders=${bidders.join(',')}`; + if (!params.length) return; + + const placementIdsArray = []; + const groupIdsArray = []; + params.forEach(paramsI => { + if (paramsI.group_id) { + groupIdsArray.push(paramsI.group_id); + } else { + if (paramsI.placement_id) placementIdsArray.push(paramsI.placement_id); + }; + }); + + const placementIds = (placementIdsArray.length && `&placements=${placementIdsArray.join(';')}`) || ''; + const groupIds = (groupIdsArray.length && `&groups=${groupIdsArray.join(';')}`) || ''; + + if (!(groupIds || placementIds)) { + return; }; - return [{ - type: 'iframe', - url: syncurl - }]; + const url = `${REPORT_ENDPOINT}?event=${eventName}&bidder=${bidder}&source=pbjs${groupIds}${placementIds}`; + + return url; }, }; +function replaceUsersyncMacros(url, gdprConsent, uspConsent) { + const { consentString, gdprApplies } = gdprConsent || {}; + + if (gdprApplies) { + const gdpr = Number(gdprApplies); + url = url.replace('{{.GDPR}}', gdpr); + + if (gdpr == 1 && consentString && consentString.length > 0) { + url = url.replace('{{.GDPRConsent}}', consentString); + } + } else { + url = url.replace('{{.GDPR}}', 0); + url = url.replace('{{.GDPRConsent}}', ''); + } + + if (uspConsent) { + url = url.replace('{{.USPrivacy}}', uspConsent); + } + + return url; +}; + function getAdEl(bid) { // best way I could think of to get El, is by matching adUnitCode to google slots... const slot = window.googletag && window.googletag.pubads && window.googletag.pubads().getSlots().find(slot => slot.getAdUnitPath() === bid.adUnitCode); @@ -342,6 +419,10 @@ function bidWonHandler(bid) { }; } +function auctionInitHandler() { + sendingDataStatistic.initEvents(); +} + function cleanWurl() { const dateNow = Date.now(); Object.keys(wurlMap).forEach(key => { @@ -353,4 +434,79 @@ function cleanWurl() { setTimeout(cleanWurl, 60000); } +function initSendingDataStatistic() { + class SendingDataStatistic { + eventNames = [ + CONSTANTS.EVENTS.BID_TIMEOUT, + CONSTANTS.EVENTS.BID_RESPONSE, + CONSTANTS.EVENTS.BID_REQUESTED, + CONSTANTS.EVENTS.NO_BID, + ]; + + disabledSending = false; + enabledSending = false; + eventHendlers = {}; + + initEvents() { + this.disabledSending = !!config.getBidderConfig()?.nextMillennium?.disabledSendingStatisticData; + if (this.disabledSending) { + this.removeEvents(); + } else { + this.createEvents(); + }; + } + + createEvents() { + if (this.enabledSending) return; + + this.enabledSending = true; + for (let eventName of this.eventNames) { + if (!this.eventHendlers[eventName]) { + this.eventHendlers[eventName] = this.eventHandler(eventName); + }; + + events.on(eventName, this.eventHendlers[eventName]); + }; + } + + removeEvents() { + if (!this.enabledSending) return; + + this.enabledSending = false; + for (let eventName of this.eventNames) { + if (!this.eventHendlers[eventName]) continue; + + events.off(eventName, this.eventHendlers[eventName]); + }; + } + + eventHandler(eventName) { + const eventHandlerFunc = this.getEventHandler(eventName); + if (eventName == CONSTANTS.EVENTS.BID_TIMEOUT) { + return bids => { + if (this.disabledSending || !Array.isArray(bids)) return; + + for (let bid of bids) { + eventHandlerFunc(bid); + }; + } + }; + + return eventHandlerFunc; + } + + getEventHandler(eventName) { + return bid => { + if (this.disabledSending) return; + + const url = spec.getUrlPixelMetric(eventName, bid); + if (!url) return; + triggerPixel(url); + }; + } + }; + + return new SendingDataStatistic(); +} + registerBidder(spec); diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index ed0c0c66a7a..397196ee7a9 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,176 +1,300 @@ -import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; -import { transformBidderParamKeywords } from '../src/utils.js'; +import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'delivery', + 'startdelay', 'skip', 'playbackmethod', 'api', 'protocol', 'boxingallowed', 'maxextended', + 'linearity', 'delivery', 'protocols', 'placement', 'minbitrate', 'maxbitrate', 'battr', 'ext']; const BIDDER_CODE = 'nexx360'; -const BIDDER_URL = 'https://fast.nexx360.io/prebid'; -const CACHE_URL = 'https://fast.nexx360.io/cache'; -const METRICS_TRACKER_URL = 'https://fast.nexx360.io/track-imp'; +const REQUEST_URL = 'https://fast.nexx360.io/booster'; + +const PAGE_VIEW_ID = utils.generateUUID(); + +const BIDDER_VERSION = '1.0'; const GVLID = 965; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['revenuemaker'], // short code + aliases: [ + { code: 'revenuemaker' }, + { code: 'first-id', gvlid: 1178 }, + { code: 'adwebone' }, + { code: 'league-m', gvlid: 965 } + ], supportedMediaTypes: [BANNER, VIDEO], - /** + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); + +/** * Determines whether or not the given bid request is valid. * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function(bid) { - if (!!bid.params.bidfloorCurrency && !['EUR', 'USD'].includes(bid.params.bidfloorCurrency)) return false; - if (!!bid.params.bidfloor && typeof bid.params.bidfloor !== 'number') return false; - if (!!bid.params.keywords && typeof bid.params.keywords !== 'object') return false; - return !!(bid.params.account && bid.params.tagId); - }, - /** +function isBidRequestValid(bid) { + return !!bid.params.tagId || !!bid.params.videoTagId; +}; + +/** * Make a server request from the list of BidRequests. * * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ - buildRequests: function(validBidRequests, bidderRequest) { - const adUnits = []; - const test = config.getConfig('debug') ? 1 : 0; - let adunitValue = null; - let userEids = null; - Object.keys(validBidRequests).forEach(key => { - adunitValue = validBidRequests[key]; - const foo = { - account: adunitValue.params.account, - tagId: adunitValue.params.tagId, - videoExt: adunitValue.params.videoExt, - label: adunitValue.adUnitCode, - bidId: adunitValue.bidId, - auctionId: adunitValue.auctionId, - transactionId: adunitValue.transactionId, - mediatypes: adunitValue.mediaTypes, - bidfloor: adunitValue.params.bidfloor || 0, - bidfloorCurrency: adunitValue.params.bidfloorCurrency || 'USD', - keywords: adunitValue.params.keywords ? transformBidderParamKeywords(adunitValue.params.keywords) : [], + +function buildRequests(bids, bidderRequest) { + const data = getBaseRequest(bids[0], bidderRequest); + bids.forEach((bid) => { + const impObject = createImpObject(bid); + if (isBannerBid(bid)) impObject.banner = getBannerObject(bid); + if (isVideoBid(bid)) impObject.video = getVideoObject(bid); + data.imp.push(impObject); + }); + return { + method: 'POST', + url: REQUEST_URL, + data, + } +} + +function createImpObject(bid) { + const floor = getFloor(bid, BANNER); + const imp = { + id: bid.bidId, + tagid: bid.adUnitCode, + ext: { + divId: bid.adUnitCode, + nexx360: { + videoTagId: bid.params.videoTagId, + tagId: bid.params.tagId, + allBids: bid.params.allBids === true, } - adUnits.push(foo); - if (adunitValue.userIdAsEids) userEids = adunitValue.userIdAsEids; + } + }; + enrichImp(imp, bid, floor); + return imp; +} + +function getBannerObject(bid) { + return { + format: toFormat(bid.mediaTypes.banner.sizes), + topframe: utils.inIframe() ? 0 : 1 + }; +} + +function getVideoObject(bid) { + let width, height; + const videoParams = utils.deepAccess(bid, `mediaTypes.video`); + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + // normalize config for video size + if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { + width = parseInt(bid.sizes[0], 10); + height = parseInt(bid.sizes[1], 10); + } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { + width = parseInt(bid.sizes[0][0], 10); + height = parseInt(bid.sizes[0][1], 10); + } else if (utils.isArray(playerSize) && playerSize.length === 2) { + width = parseInt(playerSize[0], 10); + height = parseInt(playerSize[1], 10); + } + const video = { + playerSize: [height, width], + context, + }; + + Object.keys(videoParams) + .filter(param => VIDEO_TARGETING.includes(param)) + .forEach(param => video[param] = videoParams[param]); + return video; +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function toFormat(sizes) { + return sizes.map((s) => { + return { w: s[0], h: s[1] }; + }); +} + +function getFloor(bid, mediaType) { + let floor = 0; + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' }); - const payload = { - adUnits, - // TODO: does the fallback make sense here? - href: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation) - }; - if (bidderRequest) { // modules informations (gdpr, ccpa, schain, userId) - if (bidderRequest.gdprConsent) { - payload.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - payload.gdprConsent = bidderRequest.gdprConsent.consentString; - } else { - payload.gdpr = 0; - payload.gdprConsent = ''; - } - if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } - if (bidderRequest.schain) { payload.schain = bidderRequest.schain; } - if (userEids !== null) payload.userEids = userEids; - }; - if (test) payload.test = 1; - const payloadString = JSON.stringify(payload); - return { - method: 'POST', - url: BIDDER_URL, - data: payloadString, - }; - }, - /** + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + +function enrichImp(imp, bid, floor) { + if (floor > 0) { + imp.bidfloor = floor; + imp.bidfloorcur = 'USD'; + } else if (bid.params.customFloor) { + imp.bidfloor = bid.params.customFloor; + } + if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { + imp.ext.data = bid.ortb2Imp.ext.data; + } +} + +function getBaseRequest(bid, bidderRequest) { + let req = { + id: bidderRequest.auctionId, + imp: [], + cur: [config.getConfig('currency.adServerCurrency') || 'USD'], + at: 1, + tmax: config.getConfig('bidderTimeout'), + site: { + page: bidderRequest.refererInfo.topmostLocation || bidderRequest.refererInfo.page, + domain: bidderRequest.refererInfo.domain, + }, + regs: { + coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, + }, + device: { + dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, + h: screen.height, + w: screen.width, + ua: window.navigator.userAgent, + language: window.navigator.language.split('-').shift() + }, + user: {}, + ext: { + source: 'prebid.js', + version: '$prebid.version$', + pageViewId: PAGE_VIEW_ID, + bidderVersion: BIDDER_VERSION, + } + }; + + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); + } + if (bid.params.response_template_name) { + utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); + } + req.test = config.getConfig('debug') ? 1 : 0; + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); + } + if (bidderRequest.gdprConsent.consentString !== undefined) { + utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + if (bidderRequest.gdprConsent.addtlConsent !== undefined) { + utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); + } + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (bid.schain) { + utils.deepSetValue(req, 'source.ext.schain', bid.schain); + } + if (bid.userIdAsEids) { + utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); + } + const commonFpd = bidderRequest.ortb2 || {}; + if (commonFpd.site) { + utils.mergeDeep(req, {site: commonFpd.site}); + } + if (commonFpd.user) { + utils.mergeDeep(req, {user: commonFpd.user}); + } + return req; +} + +/** * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { - const serverBody = serverResponse.body; - const bidResponses = []; - let bidResponse = null; - let value = null; - if (serverBody.hasOwnProperty('responses')) { - Object.keys(serverBody['responses']).forEach(key => { - value = serverBody['responses'][key]; - const url = `${CACHE_URL}?uuid=${value['uuid']}`; - bidResponse = { - requestId: value['bidId'], - cpm: value['cpm'], - currency: value['currency'], - width: value['width'], - height: value['height'], - ttl: value['ttl'], - creativeId: value['creativeId'], - netRevenue: true, - nexx360: { - 'ssp': value['bidder'], - 'consent': value['consent'], - 'tagId': value['tagId'] - }, - /* - meta: { - 'advertiserDomains': value['adomain'] - } - */ - }; - if (value.type === 'banner') bidResponse.adUrl = url; - if (value.type === 'video') { - const params = { - type: 'prebid', - mediatype: 'video', - ssp: value.bidder, - tag_id: value.tagId, - consent: value.consent, - price: value.cpm, - }; - bidResponse.cpm = value.cpm; - bidResponse.mediaType = 'video'; - bidResponse.vastUrl = url; - bidResponse.vastImpUrl = `${METRICS_TRACKER_URL}?${new URLSearchParams(params).toString()}`; - } - bidResponses.push(bidResponse); - }); - } - return bidResponses; - }, +function interpretResponse(response, req) { + const { bidderSettings } = getGlobal(); + const allowAlternateBidderCodes = bidderSettings && bidderSettings.standard ? bidderSettings.standard.allowAlternateBidderCodes : false; + const respBody = response.body; + if (!respBody || !Array.isArray(respBody.seatbid)) { + return []; + } + + let bids = []; + respBody.seatbid.forEach(seatbid => { + const ssp = seatbid.seat; + bids = [...bids, ...seatbid.bid.map(bid => { + const response = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bid.dealid, + currency: respBody.cur || 'USD', + netRevenue: true, + ttl: 120, + mediaType: bid.type === 'banner' ? 'banner' : 'video', + meta: { + advertiserDomains: bid.adomain, + demandSource: ssp, + }, + }; + if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ssp}`; + + if (response.mediaType === 'banner') { + response.adUrl = bid.adUrl; + } + + if (['instream', 'outstream'].includes(bid.type)) response.vastXml = bid.vastXml; + + if (bid.ext) { + response.meta.networkId = bid.ext.dsp_id; + response.meta.advertiserId = bid.ext.buyer_id; + response.meta.brandId = bid.ext.brand_id; + } + return response; + })]; + }); + return bids; +} - /** +/** * Register the user sync pixels which should be dropped after the auction. * * @param {SyncOptions} syncOptions Which user syncs are allowed? * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { - return serverResponses[0].body.cookies.slice(0, 5); - } else { - return []; - } - }, - - /** - * Register bidder specific code, which will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction - */ - onBidWon: function(bid) { - // fires a pixel to confirm a winning bid - const params = { type: 'prebid', mediatype: 'banner' }; - if (bid.hasOwnProperty('nexx360')) { - if (bid.nexx360.hasOwnProperty('ssp')) params.ssp = bid.nexx360.ssp; - if (bid.nexx360.hasOwnProperty('tagId')) params.tag_id = bid.nexx360.tagId; - if (bid.nexx360.hasOwnProperty('consent')) params.consent = bid.nexx360.consent; - }; - params.price = bid.cpm; - const url = `${METRICS_TRACKER_URL}?${new URLSearchParams(params).toString()}`; - ajax(url, null, undefined, {method: 'GET', withCredentials: true}); - return true; + return serverResponses[0].body.cookies.slice(0, 5); + } else { + return []; } - -} -registerBidder(spec); +}; diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index f0ed3c24a31..da62ce5c0a1 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -11,7 +11,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.2.13'; +const ADAPTER_VERSION = '1.4.0'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -56,6 +56,10 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { { type: 'image', url: `${MS_COOKIE_SYNC_DOMAIN}/ttd/init-sync?iab_string=${(gdprConsent && gdprConsent.consentString) || ''}&source=prebid` + }, + { + type: 'image', + url: `${MS_COOKIE_SYNC_DOMAIN}/xandr/init-sync?iab_string=${(gdprConsent && gdprConsent.consentString) || ''}&source=prebid` } ] } @@ -110,7 +114,10 @@ function buildRequests(validBidRequests, bidderRequest) { banner: { format: sizes }, - ext: bidRequest.params + ext: { + ...bidRequest.params, + timeSpentOnPage: document.timeline && document.timeline.currentTime ? document.timeline.currentTime : 0 + } }); } }); @@ -209,6 +216,6 @@ export const spec = { onBidWon, getWindowContext, onTimeout -}; +} registerBidder(spec); diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 40ef6d596be..70238294c38 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -3,7 +3,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; -import {find} from '../src/polyfill.js'; +import { find } from '../src/polyfill.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { deepClone, logError, deepAccess } from '../src/utils.js'; @@ -13,7 +13,7 @@ const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; const BIDDER_CODE = 'onetag'; const GVLID = 241; -const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); +const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); /** * Determines whether or not the given bid request is valid. @@ -53,7 +53,7 @@ export function isValid(type, bid) { function buildRequests(validBidRequests, bidderRequest) { const payload = { bids: requestsToBids(validBidRequests), - ...getPageInfo() + ...getPageInfo(bidderRequest) }; if (bidderRequest && bidderRequest.gdprConsent) { payload.gdprConsent = { @@ -61,6 +61,12 @@ function buildRequests(validBidRequests, bidderRequest) { consentRequired: bidderRequest.gdprConsent.gdprApplies }; } + if (bidderRequest && bidderRequest.gppConsent) { + payload.gppConsent = { + consentString: bidderRequest.gppConsent.gppString, + applicableSections: bidderRequest.gppConsent.applicableSections + } + } if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } @@ -74,7 +80,10 @@ function buildRequests(validBidRequests, bidderRequest) { if (storage.hasLocalStorage()) { payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); } - } catch (e) {} + } catch (e) { } + const connection = navigator.connection || navigator.webkitConnection; + payload.networkConnectionType = (connection && connection.type) ? connection.type : null; + payload.networkEffectiveConnectionType = (connection && connection.effectiveType) ? connection.effectiveType : null; return { method: 'POST', url: ENDPOINT, @@ -112,7 +121,7 @@ function interpretResponse(serverResponse, bidderRequest) { if (bid.mediaType === BANNER) { responseBid.ad = bid.ad; } else if (bid.mediaType === VIDEO) { - const {context, adUnitCode} = find(requestData.bids, (item) => + const { context, adUnitCode } = find(requestData.bids, (item) => item.bidId === bid.requestId && item.type === VIDEO ); @@ -141,7 +150,7 @@ function createRenderer(bid, rendererOptions = {}) { loaded: false }); try { - renderer.setRender(({renderer, width, height, vastXml, adUnitCode}) => { + renderer.setRender(({ renderer, width, height, vastXml, adUnitCode }) => { renderer.push(() => { window.onetag.Player.init({ ...bid, @@ -162,7 +171,6 @@ function createRenderer(bid, rendererOptions = {}) { function getFrameNesting() { let topmostFrame = window; let parent = window.parent; - let currentFrameNesting = 0; try { while (topmostFrame !== topmostFrame.parent) { parent = topmostFrame.parent; @@ -170,13 +178,8 @@ function getFrameNesting() { parent.location.href; topmostFrame = topmostFrame.parent; } - } catch (e) { - currentFrameNesting = parent === topmostFrame.top ? 1 : 2; - } - return { - topmostFrame, - currentFrameNesting - } + } catch (e) { } + return topmostFrame; } function getDocumentVisibility(window) { @@ -197,21 +200,15 @@ function getDocumentVisibility(window) { /** * Returns information about the page needed by the server in an object to be converted in JSON - * @returns {{location: *, referrer: (*|string), masked: *, wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}} + * @returns {{location: *, referrer: (*|string), stack: (*|Array.), numIframes: (*|Number), wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}} */ -function getPageInfo() { - const { topmostFrame, currentFrameNesting } = getFrameNesting(); +function getPageInfo(bidderRequest) { + const topmostFrame = getFrameNesting(); return { - location: topmostFrame.location.href, - referrer: - topmostFrame.document.referrer !== '' - ? topmostFrame.document.referrer - : null, - ancestorOrigin: - window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0 - ? window.location.ancestorOrigins[window.location.ancestorOrigins.length - 1] - : null, - masked: currentFrameNesting, + location: deepAccess(bidderRequest, 'refererInfo.page', null), + referrer: deepAccess(bidderRequest, 'refererInfo.ref', null), + stack: deepAccess(bidderRequest, 'refererInfo.stack', []), + numIframes: deepAccess(bidderRequest, 'refererInfo.numIframes', 0), wWidth: topmostFrame.innerWidth, wHeight: topmostFrame.innerHeight, oWidth: topmostFrame.outerWidth, @@ -230,7 +227,7 @@ function getPageInfo() { timing: getTiming(), version: { prebid: '$prebid.version$', - adapter: '1.1.0' + adapter: '1.1.1' } }; } @@ -344,12 +341,12 @@ function getSizes(sizes) { const ret = []; for (let i = 0; i < sizes.length; i++) { const size = sizes[i]; - ret.push({width: size[0], height: size[1]}) + ret.push({ width: size[0], height: size[1] }) } return ret; } -function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { let syncs = []; let params = ''; if (gdprConsent) { @@ -360,6 +357,11 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { params += '&gdpr_consent=' + gdprConsent.consentString; } } + if (gppConsent) { + if (typeof gppConsent.gppString === 'string') { + params += '&gpp_consent=' + gppConsent.gppString; + } + } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; } diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 14525cd0cfc..b6f4523bb50 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -268,6 +268,11 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { nocache: new Date().getTime() }; + const userAgentClientHints = deepAccess(bidderRequest, 'ortb2.device.sua'); + if (userAgentClientHints) { + defaultParams.sua = JSON.stringify(userAgentClientHints); + } + const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); if (userDataSegments.length > 0) { defaultParams.sm = userDataSegments; diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 28dba424fb2..0690bf6b4fc 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -15,23 +15,24 @@ Publishers are welcome to test the other adapter and give feedback. Please note # Bid Parameters ## Banner -| Name | Scope | Type | Description | Example -| ---- | ----- | ---- | ----------- | ------- -| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" -| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` -| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 -| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true -| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true +| Name | Scope | Type | Description | Example | +|---------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| +| `delDomain` ~~or `platform`~~** | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` | +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue

Note: OpenX suggests using the [Price Floor Module](https://docs.prebid.org/dev-docs/modules/floors.html) instead of customFloor. The Price Floor Module is prioritized over customFloor if both are present. | 1.50 | +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true | +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true | -## Video +** platform is deprecated. Please use delDomain instead. If you have any questions please contact your representative. -| Name | Scope | Type | Description | Example -| ---- | ----- | ---- | ----------- | ------- -| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" -| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" -| `openrtb` | optional | OpenRTB Impression | An OpenRtb Impression with Video subtype properties | `{ imp: [{ video: {mimes: ['video/x-ms-wmv, video/mp4']} }] }` +## Video +| Name | Scope | Type | Description | Example | +|-------------|----------|--------------------|--------------------------------------------------------------|----------------------------------------------------------------| +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" | +| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | +| `openrtb` | optional | OpenRTB Impression | An OpenRtb Impression with Video subtype properties | `{ imp: [{ video: {mimes: ['video/x-ms-wmv, video/mp4']} }] }` | # Example ```javascript diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js index 50b62544dfa..200d2cf0fed 100644 --- a/modules/openxOrtbBidAdapter.js +++ b/modules/openxOrtbBidAdapter.js @@ -1,14 +1,12 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; +import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {includes} from '../src/polyfill.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const bidderConfig = 'hb_pb_ortb'; const bidderVersion = '1.0'; -const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'delivery', - 'startdelay', 'skip', 'playbackmethod', 'api', 'protocol', 'boxingallowed', 'maxextended', - 'linearity', 'delivery', 'protocols', 'placement', 'minbitrate', 'maxbitrate', 'battr', 'ext']; export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; @@ -24,6 +22,118 @@ export const spec = { registerBidder(spec); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + if (bidRequest.mediaTypes[VIDEO]?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } + if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { + // TODO: we may want to standardize this and move fledge logic to ortbConverter + delete imp.ext.ae; + } + mergeDeep(imp, { + tagid: bidRequest.params.unit, + ext: { + divid: bidRequest.adUnitCode + } + }); + if (bidRequest.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bidRequest.params.customParams); + } + if (bidRequest.params.customFloor && !imp.bidfloor) { + imp.bidfloor = bidRequest.params.customFloor; + } + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }) + const bid = context.bidRequests[0]; + if (bid.params.doNotTrack) { + utils.deepSetValue(req, 'device.dnt', 1); + } + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); + } + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); + } + if (bid.params.response_template_name) { + utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); + } + if (bid.params.test) { + req.test = 1 + } + return req; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + if (bid.ext) { + bidResponse.meta.networkId = bid.ext.dsp_id; + bidResponse.meta.advertiserId = bid.ext.buyer_id; + bidResponse.meta.brandId = bid.ext.brand_id; + } + const {ortbResponse} = context; + if (ortbResponse.ext && ortbResponse.ext.paf) { + bidResponse.meta.paf = Object.assign({}, ortbResponse.ext.paf); + bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + return bidResponse; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + // pass these from request to the responses for use in userSync + const {ortbRequest} = context; + if (ortbRequest.ext) { + if (ortbRequest.ext.delDomain) { + utils.deepSetValue(ortbResponse, 'ext.delDomain', ortbRequest.ext.delDomain); + } + if (ortbRequest.ext.platform) { + utils.deepSetValue(ortbResponse, 'ext.platform', ortbRequest.ext.platform); + } + } + const response = buildResponse(bidResponses, ortbResponse, context); + // TODO: we may want to standardize this and move fledge logic to ortbConverter + let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return Object.assign({ + bidId, + auctionSignals: {} + }, cfg); + }); + return { + bids: response.bids, + fledgeAuctionConfigs, + } + } else { + return response.bids + } + }, + overrides: { + imp: { + bidfloor(setBidFloor, imp, bidRequest, context) { + // enforce floors should always be in USD + // TODO: does it make sense that request.cur can be any currency, but request.imp[].bidfloorcur must be USD? + const floor = {}; + setBidFloor(floor, bidRequest, {...context, currency: 'USD'}); + if (floor.bidfloorcur === 'USD') { + Object.assign(imp, floor); + } + } + } + } +}); + function transformBidParams(params, isOpenRtb) { return utils.convertTypes({ 'unit': 'string', @@ -47,190 +157,21 @@ function isBidRequestValid(bidRequest) { function buildRequests(bids, bidderRequest) { let videoBids = bids.filter(bid => isVideoBid(bid)); let bannerBids = bids.filter(bid => isBannerBid(bid)); - let requests = bannerBids.length ? [createBannerRequest(bannerBids, bidderRequest)] : []; + let requests = bannerBids.length ? [createRequest(bannerBids, bidderRequest, BANNER)] : []; videoBids.forEach(bid => { - requests.push(createVideoRequest(bid, bidderRequest)); + requests.push(createRequest([bid], bidderRequest, VIDEO)); }); return requests; } -function createBannerRequest(bids, bidderRequest) { - let data = getBaseRequest(bids[0], bidderRequest); - data.imp = bids.map(bid => { - const floor = getFloor(bid, BANNER); - let imp = { - id: bid.bidId, - tagid: bid.params.unit, - banner: { - format: toFormat(bid.mediaTypes.banner.sizes), - topframe: utils.inIframe() ? 0 : 1 - }, - ext: {divid: bid.adUnitCode} - }; - - if (bidderRequest.fledgeEnabled) { - imp.ext.ae = bid?.ortb2Imp?.ext?.ae - } - - enrichImp(imp, bid, floor); - return imp; - }); +function createRequest(bidRequests, bidderRequest, mediaType) { return { method: 'POST', url: config.getConfig('openxOrtbUrl') || REQUEST_URL, - data: data - } -} - -function toFormat(sizes) { - return sizes.map((s) => { - return { w: s[0], h: s[1] }; - }); -} - -function enrichImp(imp, bid, floor) { - if (bid.params.customParams) { - utils.deepSetValue(imp, 'ext.customParams', bid.params.customParams); - } - if (floor > 0) { - imp.bidfloor = floor; - imp.bidfloorcur = 'USD'; - } else if (bid.params.customFloor) { - imp.bidfloor = bid.params.customFloor; - } - if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { - imp.ext.data = bid.ortb2Imp.ext.data; + data: converter.toORTB({bidRequests, bidderRequest, context: {mediaType}}) } } -function createVideoRequest(bid, bidderRequest) { - let width; - let height; - const videoMediaType = utils.deepAccess(bid, `mediaTypes.video`); - const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - const floor = getFloor(bid, VIDEO); - - // normalize config for video size - if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { - width = parseInt(bid.sizes[0], 10); - height = parseInt(bid.sizes[1], 10); - } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { - width = parseInt(bid.sizes[0][0], 10); - height = parseInt(bid.sizes[0][1], 10); - } else if (utils.isArray(playerSize) && playerSize.length === 2) { - width = parseInt(playerSize[0], 10); - height = parseInt(playerSize[1], 10); - } - - let data = getBaseRequest(bid, bidderRequest); - data.imp = [{ - id: bid.bidId, - tagid: bid.params.unit, - video: { - w: width, - h: height, - topframe: utils.inIframe() ? 0 : 1 - }, - ext: {divid: bid.adUnitCode} - }]; - - enrichImp(data.imp[0], bid, floor); - - if (context) { - if (context === 'instream') { - data.imp[0].video.placement = 1; - } else if (context === 'outstream') { - data.imp[0].video.placement = 4; - } - } - - // backward compatability for video params - let videoParams = bid.params.video || bid.params.openrtb || {}; - if (utils.isArray(videoParams.imp)) { - videoParams = videoParams[0].video; - } - - Object.keys(videoParams) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => data.imp[0].video[param] = videoParams[param]); - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => data.imp[0].video[param] = videoMediaType[param]); - - return { - method: 'POST', - url: REQUEST_URL, - data: data - }; -} - -function getBaseRequest(bid, bidderRequest) { - let req = { - id: bidderRequest.auctionId, - cur: [config.getConfig('currency.adServerCurrency') || 'USD'], - at: 1, - tmax: config.getConfig('bidderTimeout'), - site: { - page: config.getConfig('pageUrl') || bidderRequest.refererInfo.referer - }, - regs: { - coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, - }, - device: { - dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, - h: screen.height, - w: screen.width, - ua: window.navigator.userAgent, - language: window.navigator.language.split('-').shift() - }, - ext: { - bc: `${bidderConfig}_${bidderVersion}` - } - }; - - if (bid.params.platform) { - utils.deepSetValue(req, 'ext.platform', bid.params.platform); - } - if (bid.params.delDomain) { - utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); - } - if (bid.params.response_template_name) { - utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); - } - if (bid.params.test) { - req.test = 1; - } - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); - } - if (bidderRequest.gdprConsent.consentString !== undefined) { - utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - if (bidderRequest.gdprConsent.addtlConsent !== undefined) { - utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); - } - } - if (bidderRequest.uspConsent) { - utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - if (bid.schain) { - utils.deepSetValue(req, 'source.ext.schain', bid.schain); - } - if (bid.userIdAsEids) { - utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); - } - const commonFpd = bidderRequest.ortb2 || {}; - if (commonFpd.site) { - utils.mergeDeep(req, {site: commonFpd.site}); - } - if (commonFpd.user) { - utils.mergeDeep(req, {user: commonFpd.user}); - } - return req; -} - function isVideoBid(bid) { return utils.deepAccess(bid, 'mediaTypes.video'); } @@ -239,107 +180,11 @@ function isBannerBid(bid) { return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); } -function getFloor(bid, mediaType) { - let floor = 0; - - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: mediaType, - size: '*' - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } - } - - return floor; -} - -function interpretOrtbResponse(resp, req) { +function interpretResponse(resp, req) { if (!resp.body) { resp.body = {nbr: 0}; } - - // pass these from request to the responses for use in userSync - if (req.data.ext) { - if (req.data.ext.delDomain) { - utils.deepSetValue(resp, 'body.ext.delDomain', req.data.ext.delDomain); - } - if (req.data.ext.platform) { - utils.deepSetValue(resp, 'body.ext.platform', req.data.ext.platform); - } - } - - const respBody = resp.body; - if (!respBody || 'nbr' in respBody || !Array.isArray(respBody.seatbid)) { - return []; - } - - let bids = []; - respBody.seatbid.forEach(seatbid => { - bids = [...bids, ...seatbid.bid.map(bid => { - let response = { - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - dealId: bid.dealid, - currency: respBody.cur || 'USD', - netRevenue: true, - ttl: 300, - mediaType: 'banner' in req.data.imp[0] ? BANNER : VIDEO, - meta: { advertiserDomains: bid.adomain } - }; - - if (response.mediaType === VIDEO) { - if (bid.nurl) { - response.vastUrl = bid.nurl; - } else { - response.vastXml = bid.adm; - } - } else { - response.ad = bid.adm; - } - - if (bid.ext) { - response.meta.networkId = bid.ext.dsp_id; - response.meta.advertiserId = bid.ext.buyer_id; - response.meta.brandId = bid.ext.brand_id; - } - - if (respBody.ext && respBody.ext.paf) { - response.meta.paf = Object.assign({}, respBody.ext.paf); - response.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); - } - - return response; - })]; - }); - - return bids; -} - -function interpretResponse(resp, req) { - const bids = interpretOrtbResponse(resp, req); - let fledgeAuctionConfigs = utils.deepAccess(resp, 'body.ext.fledge_auction_configs'); - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ - bidId, - auctionSignals: {} - }, cfg); - }); - return { - bids, - fledgeAuctionConfigs, - } - } - return bids; + return converter.fromORTB({request: req.data, response: resp.body}); } /** diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js new file mode 100644 index 00000000000..d72a8719bd8 --- /dev/null +++ b/modules/orbitsoftBidAdapter.js @@ -0,0 +1,150 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; + +const BIDDER_CODE = 'orbitsoft'; +let styleParamsMap = { + 'title.family': 'f1', // headerFont + 'title.size': 'fs1', // headerFontSize + 'title.weight': 'w1', // headerWeight + 'title.style': 's1', // headerStyle + 'title.color': 'c3', // headerColor + 'description.family': 'f2', // descriptionFont + 'description.size': 'fs2', // descriptionFontSize + 'description.weight': 'w2', // descriptionWeight + 'description.style': 's2', // descriptionStyle + 'description.color': 'c4', // descriptionColor + 'url.family': 'f3', // urlFont + 'url.size': 'fs3', // urlFontSize + 'url.weight': 'w3', // urlWeight + 'url.style': 's3', // urlStyle + 'url.color': 'c5', // urlColor + 'colors.background': 'c2', // borderColor + 'colors.border': 'c1', // borderColor + 'colors.link': 'c6', // lnkColor +}; +export const spec = { + code: BIDDER_CODE, + aliases: ['oas', '152media'], // short code and customer aliases + isBidRequestValid: function (bid) { + switch (true) { + case !('params' in bid): + utils.logError(bid.bidder + ': No required params'); + return false; + case !(bid.params.placementId): + utils.logError(bid.bidder + ': No required param placementId'); + return false; + case !(bid.params.requestUrl): + utils.logError(bid.bidder + ': No required param requestUrl'); + return false; + } + return true; + }, + buildRequests: function (validBidRequests) { + let bidRequest; + let serverRequests = []; + for (let i = 0; i < validBidRequests.length; i++) { + bidRequest = validBidRequests[i]; + let bidRequestParams = bidRequest.params; + let placementId = utils.getBidIdParameter('placementId', bidRequestParams); + let requestUrl = utils.getBidIdParameter('requestUrl', bidRequestParams); + let referrer = utils.getBidIdParameter('ref', bidRequestParams); + let location = utils.getBidIdParameter('loc', bidRequestParams); + // Append location & referrer + if (location === '') { + location = utils.getWindowLocation(); + } + if (referrer === '' && bidRequest && bidRequest.refererInfo) { + referrer = bidRequest.refererInfo.referer; + } + + // Styles params + let stylesParams = utils.getBidIdParameter('style', bidRequestParams); + let stylesParamsArray = {}; + for (let currentValue in stylesParams) { + if (stylesParams.hasOwnProperty(currentValue)) { + let currentStyle = stylesParams[currentValue]; + for (let field in currentStyle) { + if (currentStyle.hasOwnProperty(field)) { + let styleField = styleParamsMap[currentValue + '.' + field]; + if (typeof styleField !== 'undefined') { + stylesParamsArray[styleField] = currentStyle[field]; + } + } + } + } + } + // Custom params + let customParams = utils.getBidIdParameter('customParams', bidRequestParams); + let customParamsArray = {}; + for (let customField in customParams) { + if (customParams.hasOwnProperty(customField)) { + customParamsArray['c.' + customField] = customParams[customField]; + } + } + + // Sizes params (not supports by server, for future features) + let sizesParams = bidRequest.sizes; + let parsedSizes = utils.parseSizesInput(sizesParams); + let requestData = Object.assign({ + 'scid': placementId, + 'callback_uid': utils.generateUUID(), + 'loc': location, + 'ref': referrer, + 'size': parsedSizes + }, stylesParamsArray, customParamsArray); + + serverRequests.push({ + method: 'POST', + url: requestUrl, + data: requestData, + options: {withCredentials: false}, + bidRequest: bidRequest + }); + } + return serverRequests; + }, + interpretResponse: function (serverResponse, request) { + let bidResponses = []; + if (!serverResponse || serverResponse.error) { + utils.logError(BIDDER_CODE + ': Server response error'); + return bidResponses; + } + + const serverBody = serverResponse.body; + if (!serverBody) { + utils.logError(BIDDER_CODE + ': Empty bid response'); + return bidResponses; + } + + const CPM = serverBody.cpm; + const WIDTH = serverBody.width; + const HEIGHT = serverBody.height; + const CREATIVE = serverBody.content_url; + const CALLBACK_UID = serverBody.callback_uid; + const TIME_TO_LIVE = config.getConfig('_bidderTimeout'); + const REFERER = utils.getWindowTop(); + let bidRequest = request.bidRequest; + if (CPM > 0 && WIDTH > 0 && HEIGHT > 0) { + let bidResponse = { + requestId: bidRequest.bidId, + cpm: CPM, + width: WIDTH, + height: HEIGHT, + creativeId: CALLBACK_UID, + ttl: TIME_TO_LIVE, + referrer: REFERER, + currency: 'USD', + netRevenue: true, + adUrl: CREATIVE, + meta: { + advertiserDomains: serverBody.adomain ? serverBody.adomain : [] + } + }; + bidResponses.push(bidResponse); + } + + return bidResponses; + } +}; +registerBidder(spec); diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 3db1da0d689..6bcbc6a1cba 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -4,11 +4,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE, BANNER } from '../src/mediaTypes.js'; -import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray } from '../src/utils.js'; +import { NATIVE, BANNER, VIDEO } from '../src/mediaTypes.js'; +import { OUTSTREAM } from '../src/video.js'; +import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'outbrain'; const GVLID = 164; @@ -22,11 +24,12 @@ const NATIVE_PARAMS = { body: { id: 4, name: 'data', type: 2 }, cta: { id: 1, type: 12, name: 'data' } }; +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [ NATIVE, BANNER ], + supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], isBidRequestValid: (bid) => { if (typeof bid.params !== 'object') { return false; @@ -50,7 +53,7 @@ export const spec = { return ( !!config.getConfig('outbrain.bidderUrl') && - !!(bid.nativeParams || bid.sizes) + (!!(bid.nativeParams || bid.sizes) || isValidVideoRequest(bid)) ); }, buildRequests: (validBidRequests, bidderRequest) => { @@ -85,6 +88,8 @@ export const spec = { assets: getNativeAssets(bid) }) } + } else if (isVideoRequest(bid)) { + imp.video = getVideoAsset(bid); } else { imp.banner = { format: transformSizes(bid.sizes) @@ -163,7 +168,12 @@ export const spec = { return bids.map((bid, id) => { const bidResponse = bidResponses[id]; if (bidResponse) { - const type = bid.nativeParams ? NATIVE : BANNER; + let type = BANNER; + if (bid.nativeParams) { + type = NATIVE; + } else if (isVideoRequest(bid)) { + type = VIDEO; + } const bidObject = { requestId: bid.bidId, cpm: bidResponse.price, @@ -176,10 +186,16 @@ export const spec = { }; if (type === NATIVE) { bidObject.native = parseNative(bidResponse); - } else { + } else if (type === BANNER) { bidObject.ad = bidResponse.adm; bidObject.width = bidResponse.w; bidObject.height = bidResponse.h; + } else if (type === VIDEO) { + bidObject.vastXml = bidResponse.adm; + const videoContext = deepAccess(bid, 'mediaTypes.video.context'); + if (videoContext === OUTSTREAM) { + bidObject.renderer = createRenderer(bid); + } } bidObject.meta = {}; if (bidResponse.adomain && bidResponse.adomain.length > 0) { @@ -304,6 +320,27 @@ function getNativeAssets(bid) { }).filter(Boolean); } +function getVideoAsset(bid) { + const sizes = flatten(bid.mediaTypes.video.playerSize); + return { + w: parseInt(sizes[0], 10), + h: parseInt(sizes[1], 10), + protocols: bid.mediaTypes.video.protocols, + playbackmethod: bid.mediaTypes.video.playbackmethod, + mimes: bid.mediaTypes.video.mimes, + skip: bid.mediaTypes.video.skip, + delivery: bid.mediaTypes.video.delivery, + api: bid.mediaTypes.video.api, + minbitrate: bid.mediaTypes.video.minbitrate, + maxbitrate: bid.mediaTypes.video.maxbitrate, + minduration: bid.mediaTypes.video.minduration, + maxduration: bid.mediaTypes.video.maxduration, + startdelay: bid.mediaTypes.video.startdelay, + placement: bid.mediaTypes.video.placement, + linearity: bid.mediaTypes.video.linearity + }; +} + /* Turn bid request sizes into ut-compatible format */ function transformSizes(requestSizes) { if (!isArray(requestSizes)) { @@ -338,3 +375,63 @@ function _getFloor(bid, type) { } return null; } + +function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +function createRenderer(bid) { + let config = {}; + let playerUrl = OUTSTREAM_RENDERER_URL; + let render = function (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: bid.sizes, + targetId: bid.adUnitCode, + adResponse: { content: bid.vastXml } + }); + }); + }; + + let externalRenderer = deepAccess(bid, 'mediaTypes.video.renderer'); + if (!externalRenderer) { + externalRenderer = deepAccess(bid, 'renderer'); + } + + if (externalRenderer) { + config = externalRenderer.options; + playerUrl = externalRenderer.url; + render = externalRenderer.render; + } + + const renderer = Renderer.install({ + id: bid.adUnitCode, + url: playerUrl, + config: config, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + renderer.setRender(render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function isValidVideoRequest(bid) { + const videoAdUnit = deepAccess(bid, 'mediaTypes.video') + if (!videoAdUnit) { + return false; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (videoAdUnit.context == '') { + return false; + } + + return true; +} diff --git a/modules/oxxionRtdProvider.js b/modules/oxxionRtdProvider.js new file mode 100644 index 00000000000..b4964818ddf --- /dev/null +++ b/modules/oxxionRtdProvider.js @@ -0,0 +1,119 @@ +import { submodule } from '../src/hook.js' +import { deepAccess, logInfo } from '../src/utils.js' + +const oxxionRtdSearchFor = [ 'adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'userId', 'labelAny', 'adId' ]; +const LOG_PREFIX = 'oxxionRtdProvider submodule: '; + +const allAdUnits = []; + +/** @type {RtdSubmodule} */ +export const oxxionSubmodule = { + name: 'oxxionRtd', + init: init, + onAuctionEndEvent: onAuctionEnd, + getBidRequestData: getAdUnits, +}; + +function init(config, userConsent) { + if (!config.params || !config.params.domain || !config.params.contexts || !Array.isArray(config.params.contexts) || config.params.contexts.length == 0) { + return false + } + return true; +} + +function getAdUnits(reqBidsConfigObj, callback, config, userConsent) { + const reqAdUnits = reqBidsConfigObj.adUnits; + if (Array.isArray(reqAdUnits)) { + reqAdUnits.forEach(adunit => { + if (config.params.contexts.includes(deepAccess(adunit, 'mediaTypes.video.context'))) { + allAdUnits.push(adunit); + } + }); + } +} + +function insertVideoTracking(bidResponse, config, maxCpm) { + if (bidResponse.mediaType === 'video') { + const trackingUrl = getImpUrl(config, bidResponse, maxCpm); + if (!trackingUrl) { + return; + } + // Vast Impression URL + if (bidResponse.vastUrl) { + bidResponse.vastImpUrl = bidResponse.vastImpUrl + ? trackingUrl + '&url=' + encodeURI(bidResponse.vastImpUrl) + : trackingUrl + } + // Vast XML document + if (bidResponse.vastXml !== undefined) { + const doc = new DOMParser().parseFromString(bidResponse.vastXml, 'text/xml'); + const wrappers = doc.querySelectorAll('VAST Ad Wrapper, VAST Ad InLine'); + let hasAltered = false; + if (wrappers.length) { + wrappers.forEach(wrapper => { + const impression = doc.createElement('Impression'); + impression.appendChild(doc.createCDATASection(trackingUrl)); + wrapper.appendChild(impression) + }); + bidResponse.vastXml = new XMLSerializer().serializeToString(doc); + hasAltered = true; + } + if (hasAltered) { + logInfo(LOG_PREFIX + 'insert into vastXml for adId ' + bidResponse.adId); + } + } + } +} + +function getImpUrl(config, data, maxCpm) { + const adUnitCode = data.adUnitCode; + const adUnits = allAdUnits.find(adunit => adunit.code === adUnitCode && + 'mediaTypes' in adunit && + 'video' in adunit.mediaTypes && + typeof adunit.mediaTypes.video.context === 'string'); + const context = adUnits !== undefined + ? adUnits.mediaTypes.video.context + : 'unknown'; + if (!config.params.contexts.includes(context)) { + return false; + } + let trackingImpUrl = 'https://' + config.params.domain + '.oxxion.io/analytics/vast_imp?'; + trackingImpUrl += oxxionRtdSearchFor.reduce((acc, param) => { + switch (typeof data[param]) { + case 'string': + case 'number': + acc += param + '=' + data[param] + '&' + break; + } + return acc; + }, ''); + const cpmIncrement = Math.round(100000 * (data.cpm - maxCpm)) / 100000; + return trackingImpUrl + 'cpmIncrement=' + cpmIncrement + '&context=' + context; +} + +function onAuctionEnd(auctionDetails, config, userConsent) { + const transactionsToCheck = {} + auctionDetails.adUnits.forEach(adunit => { + if (config.params.contexts.includes(deepAccess(adunit, 'mediaTypes.video.context'))) { + transactionsToCheck[adunit.transactionId] = {'bids': {}, 'maxCpm': 0.0, 'secondMaxCpm': 0.0}; + } + }); + for (const key in auctionDetails.bidsReceived) { + if (auctionDetails.bidsReceived[key].transactionId in transactionsToCheck) { + transactionsToCheck[auctionDetails.bidsReceived[key].transactionId]['bids'][auctionDetails.bidsReceived[key].adId] = {'key': key, 'cpm': auctionDetails.bidsReceived[key].cpm}; + if (auctionDetails.bidsReceived[key].cpm > transactionsToCheck[auctionDetails.bidsReceived[key].transactionId]['maxCpm']) { + transactionsToCheck[auctionDetails.bidsReceived[key].transactionId]['secondMaxCpm'] = transactionsToCheck[auctionDetails.bidsReceived[key].transactionId]['maxCpm']; + transactionsToCheck[auctionDetails.bidsReceived[key].transactionId]['maxCpm'] = auctionDetails.bidsReceived[key].cpm; + } else if (auctionDetails.bidsReceived[key].cpm > transactionsToCheck[auctionDetails.bidsReceived[key].transactionId]['secondMaxCpm']) { + transactionsToCheck[auctionDetails.bidsReceived[key].transactionId]['secondMaxCpm'] = auctionDetails.bidsReceived[key].cpm; + } + } + }; + Object.keys(transactionsToCheck).forEach(transaction => { + Object.keys(transactionsToCheck[transaction]['bids']).forEach(bid => { + insertVideoTracking(auctionDetails.bidsReceived[transactionsToCheck[transaction]['bids'][bid].key], config, transactionsToCheck[transaction].secondMaxCpm); + }); + }); +} + +submodule('realTimeData', oxxionSubmodule); diff --git a/modules/oxxionRtdProvider.md b/modules/oxxionRtdProvider.md new file mode 100644 index 00000000000..061acdbe11a --- /dev/null +++ b/modules/oxxionRtdProvider.md @@ -0,0 +1,48 @@ +# Overview + +Module Name: Oxxion Rtd Provider +Module Type: Rtd Provider +Maintainer: tech@oxxion.io + +# Oxxion Real-Time-Data submodule + +Oxxion helps you to understand how your prebid stack performs. +This Rtd module is to use in order to improve video events tracking. + +# Integration + +Make sure to have the following modules listed while building prebid : `rtdModule,oxxionRtdProvider` +`rtbModule` is required to activate real-time-data submodules. +For example : +``` +gulp build --modules=schain,priceFloors,currency,consentManagement,appnexusBidAdapter,rubiconBidAdapter,rtdModule,oxxionRtdProvider +``` + +Then add the oxxion Rtd module to your prebid configuration : +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 200, + dataProviders: [ + { + name: "oxxionRtd", + waitForIt: true, + params: { + domain: "test.endpoint", + contexts: ["instream"], + } + } + ] + } + ... +) +``` + +# setConfig Parameters + +| Name | Type | Description | +|:---------------------------------|:---------|:------------------------------------------------------------------------------------------------------------| +| domain | String | This string identifies yourself in Oxxion's systems and is provided to you by your Oxxion representative. | +| contexts | Array | Array defining which video contexts to add tracking events into. Values can be instream and/or outstream. | + diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index a2cd7847564..305e175bf2c 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -14,6 +14,7 @@ import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' +export const PERMUTIVE_STANDARD_AUD_KEYWORD = 'p_standard_aud' export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) @@ -118,9 +119,21 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) { const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || [] const segmentData = getSegments(maxSegs) - acBidders.forEach(function (bidder) { + const ssps = segmentData?.ssp?.ssps ?? [] + const sspCohorts = segmentData?.ssp?.cohorts ?? [] + + const bidders = new Set([...acBidders, ...ssps]) + bidders.forEach(function (bidder) { const currConfig = { ortb2: bidderOrtb2[bidder] || {} } - const nextConfig = updateOrtbConfig(currConfig, segmentData.ac, transformationConfigs) // ORTB2 uses the `ac` segment IDs + + const isAcBidder = acBidders.indexOf(bidder) > -1 + const isSspBidder = ssps.indexOf(bidder) > -1 + + let cohorts = [] + if (isAcBidder) cohorts = segmentData.ac + if (isSspBidder) cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) + + const nextConfig = updateOrtbConfig(currConfig, cohorts, sspCohorts, transformationConfigs) bidderOrtb2[bidder] = nextConfig.ortb2; }) } @@ -131,9 +144,10 @@ export function setBidderRtb (bidderOrtb2, customModuleConfig) { * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine * the transformations on user data to include the ORTB2 object * @param {string[]} segmentIDs - Permutive segment IDs + * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs * @return {Object} Merged ortb2 object */ -function updateOrtbConfig (currConfig, segmentIDs, transformationConfigs) { +function updateOrtbConfig (currConfig, segmentIDs, sspSegmentIDs, transformationConfigs) { const name = 'permutive.com' const permutiveUserData = { @@ -154,6 +168,12 @@ function updateOrtbConfig (currConfig, segmentIDs, transformationConfigs) { deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData) + // As of writing this, only used for AppNexus/Xandr in place of appnexusAuctionKeywords in config + const currentUserKeywords = deepAccess(ortbConfig, 'ortb2.user.keywords') || '' + const keywords = sspSegmentIDs.map(segment => `${PERMUTIVE_STANDARD_AUD_KEYWORD}=${segment}`).join(',') + const updatedUserKeywords = (currentUserKeywords === '') ? keywords : `${currentUserKeywords},${keywords}` + deepSetValue(ortbConfig, 'ortb2.user.keywords', updatedUserKeywords) + return ortbConfig } @@ -222,10 +242,19 @@ function getCustomBidderFn (moduleConfig, bidder) { * @return {Object} Bidder function */ function getDefaultBidderFn (bidder) { + const isPStandardTargetingEnabled = (data, acEnabled) => { + return (acEnabled && data.ac && data.ac.length) || (data.ssp && data.ssp.cohorts && data.ssp.cohorts.length) + } + const pStandardTargeting = (data, acEnabled) => { + const ac = (acEnabled) ? (data.ac ?? []) : [] + const ssp = data?.ssp?.cohorts ?? [] + return [...new Set([...ac, ...ssp])] + } const bidderMap = { appnexus: function (bid, data, acEnabled) { - if (acEnabled && data.ac && data.ac.length) { - deepSetValue(bid, 'params.keywords.p_standard', data.ac) + if (isPStandardTargetingEnabled(data, acEnabled)) { + const segments = pStandardTargeting(data, acEnabled) + deepSetValue(bid, 'params.keywords.p_standard', segments) } if (data.appnexus && data.appnexus.length) { deepSetValue(bid, 'params.keywords.permutive', data.appnexus) @@ -234,18 +263,20 @@ function getDefaultBidderFn (bidder) { return bid }, rubicon: function (bid, data, acEnabled) { - if (acEnabled && data.ac && data.ac.length) { - deepSetValue(bid, 'params.visitor.p_standard', data.ac) + if (isPStandardTargetingEnabled(data, acEnabled)) { + const segments = pStandardTargeting(data, acEnabled) + deepSetValue(bid, 'params.visitor.p_standard', segments) } if (data.rubicon && data.rubicon.length) { - deepSetValue(bid, 'params.visitor.permutive', data.rubicon) + deepSetValue(bid, 'params.visitor.permutive', data.rubicon.map(String)) } return bid }, ozone: function (bid, data, acEnabled) { - if (acEnabled && data.ac && data.ac.length) { - deepSetValue(bid, 'params.customData.0.targeting.p_standard', data.ac) + if (isPStandardTargetingEnabled(data, acEnabled)) { + const segments = pStandardTargeting(data, acEnabled) + deepSetValue(bid, 'params.customData.0.targeting.p_standard', segments) } return bid @@ -280,19 +311,29 @@ export function isPermutiveOnPage () { * @return {Object} */ export function getSegments (maxSegs) { - const legacySegs = readSegments('_psegs').map(Number).filter(seg => seg >= 1000000).map(String) - const _ppam = readSegments('_ppam') - const _pcrprs = readSegments('_pcrprs') + const legacySegs = readSegments('_psegs', []).map(Number).filter(seg => seg >= 1000000).map(String) + const _ppam = readSegments('_ppam', []) + const _pcrprs = readSegments('_pcrprs', []) const segments = { ac: [..._pcrprs, ..._ppam, ...legacySegs], - rubicon: readSegments('_prubicons'), - appnexus: readSegments('_papns'), - gam: readSegments('_pdfps'), + rubicon: readSegments('_prubicons', []), + appnexus: readSegments('_papns', []), + gam: readSegments('_pdfps', []), + ssp: readSegments('_pssps', { + cohorts: [], + ssps: [] + }), } for (const bidder in segments) { - segments[bidder] = segments[bidder].slice(0, maxSegs) + if (bidder === 'ssp') { + if (segments[bidder].cohorts && Array.isArray(segments[bidder].cohorts)) { + segments[bidder].cohorts = segments[bidder].cohorts.slice(0, maxSegs) + } + } else { + segments[bidder] = segments[bidder].slice(0, maxSegs) + } } return segments @@ -300,15 +341,17 @@ export function getSegments (maxSegs) { /** * Gets an array of segment IDs from LocalStorage - * or returns an empty array + * or return the default value provided. + * @template A * @param {string} key - * @return {string[]|number[]} + * @param {A} defaultValue + * @return {A} */ -function readSegments (key) { +function readSegments (key, defaultValue) { try { - return JSON.parse(storage.getDataFromLocalStorage(key) || '[]') + return JSON.parse(storage.getDataFromLocalStorage(key)) || defaultValue } catch (e) { - return [] + return defaultValue } } diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index f99389f82cc..7d8f073357c 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -45,17 +45,52 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd | params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` | | params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | | params.transformations | Object[] | An array of configurations for ORTB2 user data transformations | | +| params.overwrites | Object | An object specifying functions for custom targeting logic for bidders. | - | ##### The `transformations` parameter This array contains configurations for transformations we'll apply to the Permutive object in the ORTB2 `user.data` array. The results of these transformations will be appended to the `user.data` array that's attached to ORTB2 bid requests. -##### Supported transformations +###### Supported transformations | Name | ID | Config structure | Description | |----------------|-----|---------------------------------------------------|--------------------------------------------------------------------------------------| | IAB taxonomies | iab | { segtax: number, iabIds: Object} | Transform segment IDs from Permutive to IAB (note: alpha version, subject to change) | +##### The `overwrites` parameter + +The keys for this object should match a bidder (e.g. `rubicon`), which then can define a function to overwrite the customer targeting logic. + +```javascript +{ + params: { + overwrites: { + rubicon: function customTargeting(bid, data, acEnabled, utils, defaultFn) { + if (defaultFn) { + bid = defaultFn(bid, data, acEnabled) + } + if (data.gam && data.gam.length) { + utils.deepSetValue(bid, 'params.visitor.permutive', data.gam) + } + } + } + } +} +``` + +###### `customTargeting` function parameters + +| Name | Description | +|--------------|--------------------------------------------------------------------------------| +| `bid` | The bid request object. | +| `data` | Permutive's targeting data read from localStorage. | +| `acEnabled` | Boolean stating whether Audience Connect is enabled via `acBidders`. | +| `utils` | An object of helpful utilities. `(deepSetValue, deepAccess, isFn, mergeDeep)`. | +| `defaultFn` | The default targeting function. | + + + + #### Context Permutive is not listed as a TCF vendor as all data collection is on behalf of the publisher and based on consent the publisher has received from the user. diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 4c4935e93a4..41b1151e72a 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -1,8 +1,8 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {find, includes} from '../src/polyfill.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { find, includes } from '../src/polyfill.js'; import { convertCamelToUnderscore, deepAccess, @@ -13,14 +13,17 @@ import { isPlainObject, transformBidderParamKeywords } from '../src/utils.js'; -import {auctionManager} from '../src/auctionManager.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import { auctionManager } from '../src/auctionManager.js'; const SOURCE = 'pbjs'; const storageManager = getStorageManager({bidderCode: 'pixfuture'}); const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +let pixID = ''; +const GVLID = 839; + export const spec = { code: 'pixfuture', + gvlid: GVLID, hostname: 'https://gosrv.pixfuture.com', getHostname() { @@ -41,6 +44,10 @@ export const spec = { const tags = validBidRequests.map(bidToTag); const hostname = this.getHostname(); return validBidRequests.map((bidRequest) => { + if (bidRequest.params.pix_id) { + pixID = bidRequest.params.pix_id + } + let referer = ''; if (bidderRequest && bidderRequest.refererInfo) { referer = bidderRequest.refererInfo.page || ''; @@ -123,7 +130,7 @@ export const spec = { const ret = { url: `${hostname}/pixservices`, method: 'POST', - options: {withCredentials: false}, + options: {withCredentials: true}, data: { v: $$PREBID_GLOBAL$$.version, pageUrl: referer, @@ -162,15 +169,39 @@ export const spec = { return bids; }, - getUserSyncs: function (syncOptions, bid, gdprConsent) { - var pixid = ''; - if (typeof bid[0] === 'undefined' || bid[0] === null) { pixid = '0'; } else { pixid = bid[0].body.pix_id; } - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { - return [{ + getUserSyncs: function (syncOptions, bid, gdprConsent, uspConsent) { + const syncs = []; + + let syncurl = 'pixid=' + pixID; + let gdpr = (gdprConsent && gdprConsent.gdprApplies) ? 1 : 0; + let consent = gdprConsent ? encodeURIComponent(gdprConsent.consentString || '') : ''; + + // Attaching GDPR Consent Params in UserSync url + syncurl += '&gdprconcent=' + gdpr + '&adsync=' + consent; + + // CCPA + if (uspConsent) { + syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + // coppa compliance + if (config.getConfig('coppa') === true) { + syncurl += '&coppa=1'; + } + + if (syncOptions.iframeEnabled) { + syncs.push({ type: 'iframe', - url: 'https://gosrv.pixfuture.com/cookiesync?adsync=' + gdprConsent.consentString + '&pixid=' + pixid + '&gdprconcent=' + gdprConsent.gdprApplies - }]; + url: 'https://gosrv.pixfuture.com/cookiesync?f=b&' + syncurl + }); + } else { + syncs.push({ + type: 'image', + url: 'https://gosrv.pixfuture.com/cookiesync?f=i&' + syncurl + }); } + + return syncs; } }; diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 4d77a5ddbf0..36f1aa9cbe7 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,19 +1,11 @@ import Adapter from '../../src/adapter.js'; -import {createBid} from '../../src/bidfactory.js'; import { bind, - createTrackPixelHtml, - deepAccess, deepClone, - deepSetValue, flatten, generateUUID, - getBidRequest, - getDefinedParams, getPrebidInternal, insertUserSyncIframe, - isArray, - isEmpty, isNumber, isPlainObject, isStr, @@ -21,34 +13,27 @@ import { logInfo, logMessage, logWarn, - mergeDeep, - parseSizesInput, - timestamp, triggerPixel, - uniques + uniques, + deepAccess, } from '../../src/utils.js'; import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; -import {NATIVE, VIDEO} from '../../src/mediaTypes.js'; import {isValid} from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; -import {find, includes} from '../../src/polyfill.js'; +import {includes} from '../../src/polyfill.js'; import {S2S_VENDORS} from './config.js'; import {ajax} from '../../src/ajax.js'; import {hook} from '../../src/hook.js'; -import {getGlobal} from '../../src/prebidGlobal.js'; import {hasPurpose1Consent} from '../../src/utils/gpdr.js'; +import {buildPBSRequest, interpretPBSResponse} from './ortbConverter.js'; import {useMetrics} from '../../src/utils/perfMetrics.js'; const getConfig = config.getConfig; const TYPE = CONSTANTS.S2S.SRC; let _syncCount = 0; -const DEFAULT_S2S_TTL = 60; -const DEFAULT_S2S_CURRENCY = 'USD'; -const DEFAULT_S2S_NETREVENUE = true; - let _s2sConfigs; let eidPermissions; @@ -95,12 +80,13 @@ let eidPermissions; * @property {boolean} [cacheMarkup] whether to cache the adm result * @property {string} [syncEndpoint] endpoint URL for syncing cookies * @property {Object} [extPrebid] properties will be merged into request.ext.prebid + * @property {Object} [ortbNative] base value for imp.native.request */ /** * @type {S2SDefaultConfig} */ -const s2sDefaultConfig = { +export const s2sDefaultConfig = { bidders: Object.freeze([]), timeout: 1000, syncTimeout: 1000, @@ -108,7 +94,14 @@ const s2sDefaultConfig = { adapter: 'prebidServer', allowUnknownBidderCodes: false, adapterOptions: {}, - syncUrlModifier: {} + syncUrlModifier: {}, + ortbNative: { + context: 1, + plcmttype: 1, + eventtrackers: [ + {event: 1, methods: [1]} + ], + } }; config.setDefaults({ @@ -223,17 +216,30 @@ export function resetSyncedStatus() { /** * @param {Array} bidderCodes list of bidders to request user syncs for. */ -function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig, storedRequests) { +function queueSync(bidderCodes, gdprConsent, uspConsent, gppConsent, s2sConfig, storedRequests) { if (_s2sConfigs.length === _syncCount) { return; } _syncCount++; + let filterSettings = {}; + const userSyncFilterSettings = getConfig('userSync.filterSettings'); + + if (userSyncFilterSettings) { + const { all, iframe, image } = userSyncFilterSettings; + const ifrm = iframe || all; + const img = image || all; + + if (ifrm) filterSettings = Object.assign({ iframe: ifrm }, filterSettings); + if (img) filterSettings = Object.assign({ image: img }, filterSettings); + } + const payload = { uuid: generateUUID(), bidders: bidderCodes, account: s2sConfig.accountId, - storedRequests + storedRequests, + filterSettings }; let userSyncLimit = s2sConfig.userSyncLimit; @@ -254,6 +260,15 @@ function queueSync(bidderCodes, gdprConsent, uspConsent, s2sConfig, storedReques payload.us_privacy = uspConsent; } + if (gppConsent) { + // proposing the following formatting, can adjust if needed... + // update - leaving this param as an array, since it's part of a POST payload where the [] characters shouldn't matter too much + payload.gpp_sid = gppConsent.applicableSections + // should we add check if applicableSections was not equal to -1 (where user was out of scope)? + // this would be similar to what was done above for TCF + payload.gpp = gppConsent.gppString; + } + if (typeof s2sConfig.coopSync === 'boolean') { payload.coopSync = s2sConfig.coopSync; } @@ -338,7 +353,7 @@ function doBidderSync(type, url, bidder, done, timeout) { * * @param {Array} bidders a list of bidder names */ -function doClientSideSyncs(bidders, gdprConsent, uspConsent) { +function doClientSideSyncs(bidders, gdprConsent, uspConsent, gppConsent) { bidders.forEach(bidder => { let clientAdapter = adapterManager.getBidAdapter(bidder); if (clientAdapter && clientAdapter.registerSyncs) { @@ -349,104 +364,14 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent) { clientAdapter, [], gdprConsent, - uspConsent + uspConsent, + gppConsent ) ); } }); } -function _appendSiteAppDevice(request, pageUrl, accountId) { - if (!request) return; - - // ORTB specifies app OR site - if (typeof config.getConfig('app') === 'object') { - request.app = config.getConfig('app'); - request.app.publisher = {id: accountId}; - } else { - request.site = {}; - if (isPlainObject(config.getConfig('site'))) { - request.site = config.getConfig('site'); - } - // set publisher.id if not already defined - if (!deepAccess(request.site, 'publisher.id')) { - deepSetValue(request.site, 'publisher.id', accountId); - } - // set site.page if not already defined - if (!request.site.page) { - request.site.page = pageUrl; - } - } - if (typeof config.getConfig('device') === 'object') { - request.device = config.getConfig('device'); - } - if (!request.device) { - request.device = {}; - } - if (!request.device.w) { - request.device.w = window.innerWidth; - } - if (!request.device.h) { - request.device.h = window.innerHeight; - } -} - -function addBidderFirstPartyDataToRequest(request, bidderFpd) { - const fpdConfigs = Object.entries(bidderFpd).reduce((acc, [bidder, bidderOrtb2]) => { - const ortb2 = mergeDeep({}, bidderOrtb2); - acc.push({ - bidders: [ bidder ], - config: { ortb2 } - }); - return acc; - }, []); - - if (fpdConfigs.length) { - deepSetValue(request, 'ext.prebid.bidderconfig', fpdConfigs); - } -} - -// https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40 -let nativeDataNames = Object.keys(CONSTANTS.PREBID_NATIVE_DATA_KEYS_TO_ORTB); - -// returns object with legacy asset name as key and asset id as value: -// { "sponsoredBy": 1, ... } -let nativeDataIdMap = nativeDataNames.reduce((prev, key) => { - prev[key] = CONSTANTS.NATIVE_ASSET_TYPES[CONSTANTS.PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]]; - return prev; -}, {}); - -let nativeImgIdMap = { - icon: 1, - image: 3 -}; - -let nativeEventTrackerEventMap = { - impression: 1, - 'viewable-mrc50': 2, - 'viewable-mrc100': 3, - 'viewable-video50': 4, -}; - -let nativeEventTrackerMethodMap = { - img: 1, - js: 2 -}; - -if (FEATURES.NATIVE) { - // enable reverse lookup - [ - nativeDataIdMap, - nativeImgIdMap, - nativeEventTrackerEventMap, - nativeEventTrackerMethodMap - ].forEach(map => { - Object.keys(map).forEach(key => { - map[map[key]] = key; - }); - }); -} - /** * map wurl to auction id and adId for use in the BID_WON event */ @@ -463,18 +388,6 @@ function addWurl(auctionId, adId, wurl) { } } -function getPbsResponseData(bidderRequests, response, pbsName, pbjsName) { - const bidderValues = deepAccess(response, `ext.${pbsName}`); - if (bidderValues) { - Object.keys(bidderValues).forEach(bidder => { - let biddersReq = find(bidderRequests, bidderReq => bidderReq.bidderCode === bidder); - if (biddersReq) { - biddersReq[pbjsName] = bidderValues[bidder]; - } - }); - } -} - /** * @param {string} auctionId * @param {string} adId generated value set to bidObject.adId by bidderFactory Bid() @@ -502,598 +415,6 @@ export function resetWurlMap() { wurlMap = {}; } -function ORTB2(s2sBidRequest, bidderRequests, adUnits, requestedBidders) { - this.s2sBidRequest = s2sBidRequest; - this.bidderRequests = bidderRequests; - this.adUnits = adUnits; - this.s2sConfig = s2sBidRequest.s2sConfig; - this.requestedBidders = requestedBidders; - - this.bidIdMap = {}; - this.adUnitsByImp = {}; - this.impRequested = {}; - this.auctionId = bidderRequests.map(br => br.auctionId).reduce((l, r) => (l == null || l === r) && r); - this.requestTimestamp = timestamp(); -} - -Object.assign(ORTB2.prototype, { - buildRequest() { - const {s2sBidRequest, bidderRequests: bidRequests, adUnits, s2sConfig, requestedBidders} = this; - - let imps = []; - let aliases = {}; - const firstBidRequest = bidRequests[0]; - let floorMin = null; - let floorMinCur = null; - - // transform ad unit into array of OpenRTB impression objects - let impIds = new Set(); - adUnits.forEach(adUnit => { - // TODO: support labels / conditional bids - // for now, just warn about them - adUnit.bids.forEach((bid) => { - if (bid.mediaTypes != null) { - logWarn(`Prebid Server adapter does not (yet) support bidder-specific mediaTypes for the same adUnit. Size mapping configuration will be ignored for adUnit: ${adUnit.code}, bidder: ${bid.bidder}`); - } - }); - - // in case there is a duplicate imp.id, add '-2' suffix to the second imp.id. - // e.g. if there are 2 adUnits (case of twin adUnit codes) with code 'test', - // first imp will have id 'test' and second imp will have id 'test-2' - let impressionId = adUnit.code; - let i = 1; - while (impIds.has(impressionId)) { - i++; - impressionId = `${adUnit.code}-${i}`; - } - impIds.add(impressionId); - this.adUnitsByImp[impressionId] = adUnit; - - const videoParams = deepAccess(adUnit, 'mediaTypes.video'); - const bannerParams = deepAccess(adUnit, 'mediaTypes.banner'); - - adUnit.bids.forEach(bid => { - this.setBidRequestId(impressionId, bid.bidder, bid.bid_id); - // check for and store valid aliases to add to the request - if (adapterManager.aliasRegistry[bid.bidder]) { - const bidder = adapterManager.bidderRegistry[bid.bidder]; - // adding alias only if alias source bidder exists and alias isn't configured to be standalone - // pbs adapter - if (bidder && !bidder.getSpec().skipPbsAliasing) { - aliases[bid.bidder] = adapterManager.aliasRegistry[bid.bidder]; - } - } - }); - - let mediaTypes = {}; - if (bannerParams && bannerParams.sizes) { - const sizes = parseSizesInput(bannerParams.sizes); - - // get banner sizes in form [{ w: , h: }, ...] - const format = sizes.map(size => { - const [ width, height ] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); - - mediaTypes['banner'] = {format}; - - if (bannerParams.pos) mediaTypes['banner'].pos = bannerParams.pos; - } - - if (!isEmpty(videoParams)) { - if (videoParams.context === 'outstream' && !videoParams.renderer && !adUnit.renderer) { - // Don't push oustream w/o renderer to request object. - logError('Outstream bid without renderer cannot be sent to Prebid Server.'); - } else { - if (videoParams.context === 'instream' && !videoParams.hasOwnProperty('placement')) { - videoParams.placement = 1; - } - - mediaTypes['video'] = Object.keys(videoParams).filter(param => param !== 'context') - .reduce((result, param) => { - if (param === 'playerSize') { - result.w = deepAccess(videoParams, `${param}.0.0`); - result.h = deepAccess(videoParams, `${param}.0.1`); - } else { - result[param] = videoParams[param]; - } - return result; - }, {}); - } - } - const nativeReq = deepAccess(adUnit, 'nativeOrtbRequest'); - if (FEATURES.NATIVE && nativeReq) { - const defaultRequest = { - // TODO: determine best way to pass these and if we allow defaults - context: 1, - plcmttype: 1, - eventtrackers: [ - { event: 1, methods: [1] } - ], - // TODO: figure out how to support privacy field - // privacy: int - }; - try { - const request = Object.assign(defaultRequest, nativeReq); - mediaTypes[NATIVE] = { - request: JSON.stringify(request), - ver: '1.2' - }; - } catch (e) { - logError('error creating native request: ' + String(e)); - } - } - - // get bidder params in form { : {...params} } - // initialize reduce function with the user defined `ext` properties on the ad unit - const ext = adUnit.bids.reduce((acc, bid) => { - if (bid.bidder == null) return acc; - const adapter = adapterManager.bidderRegistry[bid.bidder]; - if (adapter && adapter.getSpec().transformBidParams) { - bid.params = adapter.getSpec().transformBidParams(bid.params, true, adUnit, bidRequests); - } - deepSetValue(acc, - `prebid.bidder.${bid.bidder}`, - (s2sConfig.adapterOptions && s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, s2sConfig.adapterOptions[bid.bidder]) : bid.params - ); - return acc; - }, {...deepAccess(adUnit, 'ortb2Imp.ext')}); - - const imp = { ...adUnit.ortb2Imp, id: impressionId, ext, secure: s2sConfig.secure }; - - const ortb2 = {...deepAccess(adUnit, 'ortb2Imp.ext.data')}; - Object.keys(ortb2).forEach(prop => { - /** - * Prebid AdSlot - * @type {(string|undefined)} - */ - if (prop === 'pbadslot') { - if (typeof ortb2[prop] === 'string' && ortb2[prop]) { - deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); - } else { - // remove pbadslot property if it doesn't meet the spec - delete imp.ext.data.pbadslot; - } - } else if (prop === 'adserver') { - /** - * Copy GAM AdUnit and Name to imp - */ - ['name', 'adslot'].forEach(name => { - /** @type {(string|undefined)} */ - const value = deepAccess(ortb2, `adserver.${name}`); - if (typeof value === 'string' && value) { - deepSetValue(imp, `ext.data.adserver.${name.toLowerCase()}`, value); - } - }); - } else { - deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); - } - }); - - mergeDeep(imp, mediaTypes); - - const convertCurrency = typeof getGlobal().convertCurrency !== 'function' - ? (amount) => amount - : (amount, from, to) => { - if (from === to) return amount; - let result = null; - try { - result = getGlobal().convertCurrency(amount, from, to); - } catch (e) { - } - return result; - } - - const floor = (() => { - // we have to pick a floor for the imp - here we attempt to find the minimum floor - // across all bids for this adUnit - const s2sCurrency = config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY; - - return adUnit.bids - .map((bid) => this.getBidRequest(imp.id, bid.bidder)) - .map((bid) => { - if (!bid || typeof bid.getFloor !== 'function') return; - try { - const {currency, floor} = bid.getFloor({ - currency: s2sCurrency - }); - - return { - currency, - floor: parseFloat(floor) - } - } catch (e) { - logError('PBS: getFloor threw an error: ', e); - } - }) - .reduce((min, floor) => { - // if any bid does not have a valid floor, do not attempt to send any to PBS - if (floor == null || floor.currency == null || floor.floor == null || isNaN(floor.floor)) { - min.min = null; - } - if (min.min === null) { - return min; - } - // otherwise, pick the minimum one (or, in some strange confluence of circumstances, the one in the best currency) - if (min.ref == null) { - min.ref = min.min = floor; - } else { - const value = convertCurrency(floor.floor, floor.currency, min.ref.currency); - - if (value != null && value < min.ref.floor) { - min.ref.floor = value; - min.min = floor; - } - } - - return min; - }, {}).min - })(); - - if (floor) { - imp.bidfloor = floor.floor; - imp.bidfloorcur = floor.currency; - - // logic below relates to https://github.com/prebid/Prebid.js/issues/8749 and does the following: - // 1. check client-side floors (ref bidfloor/bidfloorcur & ortb2Imp floorMin/floorMinCur (if present)) - // 2. set pbs req wide floorMinCur to the first floor currency found when iterating over imp's - // (if currency conversion logic present, convert all imp floor values to this currency) - // 3. compare/store ref to lowest floorMin value as each imp is iterated over - // 4. set req wide floorMin and floorMinCur values for pbs after iterations are done - if (floorMinCur == null) { floorMinCur = floor.currency } - const ortb2ImpFloorMin = imp.ext?.prebid?.floors?.floorMin || imp.ext?.prebid?.floorMin; - const ortb2ImpFloorCur = imp.ext?.prebid?.floors?.floorMinCur || imp.ext?.prebid?.floorMinCur || floorMinCur; - - const convertedFloorMinValue = convertCurrency(floor.floor, floor.currency, floorMinCur); - const convertedOrtb2ImpFloorMinValue = ortb2ImpFloorMin && ortb2ImpFloorCur ? convertCurrency(ortb2ImpFloorMin, ortb2ImpFloorCur, floorMinCur) : false; - - const lowestImpFloorMin = convertedOrtb2ImpFloorMinValue && convertedOrtb2ImpFloorMinValue < convertedFloorMinValue - ? convertedOrtb2ImpFloorMinValue - : convertedFloorMinValue; - - deepSetValue(imp, 'ext.prebid.floors.floorMin', lowestImpFloorMin); - if (floorMin == null || floorMin > lowestImpFloorMin) { floorMin = lowestImpFloorMin } - } - - if (imp.banner || imp.video || imp.native) { - imps.push(imp); - } - }); - - if (!imps.length) { - logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.'); - return; - } - const request = { - id: firstBidRequest.auctionId, - source: {tid: firstBidRequest.auctionId}, - tmax: s2sConfig.timeout, - imp: imps, - // to do: add setconfig option to pass test = 1 - test: 0, - ext: { - prebid: { - // set ext.prebid.auctiontimestamp with the auction timestamp. Data type is long integer. - auctiontimestamp: firstBidRequest.auctionStart, - targeting: { - // includewinners is always true for openrtb - includewinners: true, - // includebidderkeys always false for openrtb - includebidderkeys: false - } - } - } - }; - - // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check - if (typeof deepAccess(firstBidRequest, 'bids.0.floorData') === 'object') { - request.ext.prebid.floors = { enabled: false }; - } - - // This is no longer overwritten unless name and version explicitly overwritten by extPrebid (mergeDeep) - request.ext.prebid = Object.assign(request.ext.prebid, {channel: {name: 'pbjs', version: $$PREBID_GLOBAL$$.version}}); - - // set debug flag if in debug mode - if (getConfig('debug')) { - request.ext.prebid = Object.assign(request.ext.prebid, {debug: true}); - } - - // s2sConfig video.ext.prebid is passed through openrtb to PBS - if (s2sConfig.extPrebid && typeof s2sConfig.extPrebid === 'object') { - request.ext.prebid = mergeDeep(request.ext.prebid, s2sConfig.extPrebid); - } - - // get reference to pbs config schain bidder names (if any exist) - const pbsSchainBidderNamesArr = request.ext.prebid?.schains ? request.ext.prebid.schains.flatMap(s => s.bidders) : []; - // create an schains object - const schains = Object.fromEntries( - (request.ext.prebid?.schains || []).map(({bidders, schain}) => [JSON.stringify(schain), {bidders: new Set(bidders), schain}]) - ); - - // compare bidder specific schains with pbs specific schains - request.ext.prebid.schains = Object.values( - bidRequests - .map((req) => [req.bidderCode, req.bids[0].schain]) - .reduce((chains, [bidder, chain]) => { - const chainKey = JSON.stringify(chain); - - switch (true) { - // if pbjs bidder name is same as pbs bidder name, pbs bidder name always wins - case chainKey && pbsSchainBidderNamesArr.indexOf(bidder) !== -1: - logInfo(`bidder-specific schain for ${bidder} skipped due to existing entry`); - break; - // if a pbjs schain obj is equal to an schain obj that exists on the pbs side, add the bidder name on the pbs side - case chainKey && chains.hasOwnProperty(chainKey) && pbsSchainBidderNamesArr.indexOf(bidder) === -1: - chains[chainKey].bidders.add(bidder); - break; - // if a pbjs schain obj is not on the pbs side, add a new schain entry on the pbs side - case chainKey && !chains.hasOwnProperty(chainKey): - chains[chainKey] = {bidders: new Set(), schain: chain}; - chains[chainKey].bidders.add(bidder); - break; - default: - } - - return chains; - }, schains) - ).map(({bidders, schain}) => ({bidders: Array.from(bidders), schain})); - // if schains evaluates to an empty array, remove it from the prebid object - if (request.ext.prebid.schains.length === 0) delete request.ext.prebid.schains; - - /** - * @type {(string[]|string|undefined)} - OpenRTB property 'cur', currencies available for bids - */ - const adServerCur = config.getConfig('currency.adServerCurrency'); - if (adServerCur && typeof adServerCur === 'string') { - // if the value is a string, wrap it with an array - request.cur = [adServerCur]; - } else if (Array.isArray(adServerCur) && adServerCur.length) { - // if it's an array, get the first element - request.cur = [adServerCur[0]]; - } - - _appendSiteAppDevice(request, bidRequests[0].refererInfo.page, s2sConfig.accountId); - - // pass schain object if it is present - const schain = deepAccess(bidRequests, '0.bids.0.schain'); - if (schain) { - request.source.ext = { - schain: schain - }; - } - - if (!isEmpty(aliases)) { - request.ext.prebid.aliases = {...request.ext.prebid.aliases, ...aliases}; - } - - const bidUserIdAsEids = deepAccess(bidRequests, '0.bids.0.userIdAsEids'); - if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { - deepSetValue(request, 'user.ext.eids', bidUserIdAsEids); - } - - if (isArray(eidPermissions) && eidPermissions.length > 0) { - if (requestedBidders && isArray(requestedBidders)) { - eidPermissions.forEach(i => { - if (i.bidders) { - i.bidders = i.bidders.filter(bidder => includes(requestedBidders, bidder)) - } - }); - } - deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); - } - - const multibid = config.getConfig('multibid'); - if (multibid) { - deepSetValue(request, 'ext.prebid.multibid', multibid.reduce((result, i) => { - let obj = {}; - - Object.keys(i).forEach(key => { - obj[key.toLowerCase()] = i[key]; - }); - - result.push(obj); - - return result; - }, [])); - } - - if (bidRequests) { - if (firstBidRequest.gdprConsent) { - // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module - let gdprApplies; - if (typeof firstBidRequest.gdprConsent.gdprApplies === 'boolean') { - gdprApplies = firstBidRequest.gdprConsent.gdprApplies ? 1 : 0; - } - deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - deepSetValue(request, 'user.ext.consent', firstBidRequest.gdprConsent.consentString); - if (firstBidRequest.gdprConsent.addtlConsent && typeof firstBidRequest.gdprConsent.addtlConsent === 'string') { - deepSetValue(request, 'user.ext.ConsentedProvidersSettings.consented_providers', firstBidRequest.gdprConsent.addtlConsent); - } - } - - // US Privacy (CCPA) support - if (firstBidRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', firstBidRequest.uspConsent); - } - } - - if (getConfig('coppa') === true) { - deepSetValue(request, 'regs.coppa', 1); - } - - const commonFpd = s2sBidRequest.ortb2Fragments?.global || {}; - mergeDeep(request, commonFpd); - - addBidderFirstPartyDataToRequest(request, s2sBidRequest.ortb2Fragments?.bidder || {}); - - request.imp.forEach((imp) => this.impRequested[imp.id] = imp); - - if (request.ext?.prebid?.floors?.enabled) { - request.ext.prebid.floors.floorMin = floorMin; - request.ext.prebid.floors.floorMinCur = floorMinCur; - } - - return request; - }, - - interpretResponse(response) { - const {bidderRequests, s2sConfig} = this; - const bids = []; - - [['errors', 'serverErrors'], ['responsetimemillis', 'serverResponseTimeMs']] - .forEach(info => getPbsResponseData(bidderRequests, response, info[0], info[1])) - - if (response.seatbid) { - // a seatbid object contains a `bid` array and a `seat` string - response.seatbid.forEach(seatbid => { - (seatbid.bid || []).forEach(bid => { - let bidRequest = this.getBidRequest(bid.impid, seatbid.seat); - if (bidRequest == null) { - if (!s2sConfig.allowUnknownBidderCodes) { - logWarn(`PBS adapter received bid from unknown bidder (${seatbid.seat}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`); - return; - } - // for stored impression, a request was made with bidder code `null`. Pick it up here so that NO_BID, BID_WON, etc events - // can work as expected (otherwise, the original request will always result in NO_BID). - bidRequest = this.getBidRequest(bid.impid, null); - } - - const cpm = bid.price; - const status = cpm !== 0 ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID; - let bidObject = createBid(status, { - bidder: seatbid.seat, - src: TYPE, - bidId: bidRequest ? (bidRequest.bidId || bidRequest.bid_Id) : null, - transactionId: this.adUnitsByImp[bid.impid].transactionId, - auctionId: this.auctionId, - }); - bidObject.requestTimestamp = this.requestTimestamp; - bidObject.cpm = cpm; - if (bid?.ext?.prebid?.meta?.adaptercode) { - bidObject.adapterCode = bid.ext.prebid.meta.adaptercode; - } else if (bidRequest?.bidder) { - bidObject.adapterCode = bidRequest.bidder; - } else { - bidObject.adapterCode = seatbid.seat; - } - - // temporarily leaving attaching it to each bidResponse so no breaking change - // BUT: this is a flat map, so it should be only attached to bidderRequest, a the change above does - let serverResponseTimeMs = deepAccess(response, ['ext', 'responsetimemillis', seatbid.seat].join('.')); - if (bidRequest && serverResponseTimeMs) { - bidRequest.serverResponseTimeMs = serverResponseTimeMs; - } - - // Look for seatbid[].bid[].ext.prebid.bidid and place it in the bidResponse object for use in analytics adapters as 'pbsBidId' - const bidId = deepAccess(bid, 'ext.prebid.bidid'); - if (isStr(bidId)) { - bidObject.pbsBidId = bidId; - } - - // store wurl by auctionId and adId so it can be accessed from the BID_WON event handler - if (isStr(deepAccess(bid, 'ext.prebid.events.win'))) { - addWurl(this.auctionId, bidObject.adId, deepAccess(bid, 'ext.prebid.events.win')); - } - - let extPrebidTargeting = deepAccess(bid, 'ext.prebid.targeting'); - - // If ext.prebid.targeting exists, add it as a property value named 'adserverTargeting' - // The removal of hb_winurl and hb_bidid targeting values is temporary - // once we get through the transition, this block will be removed. - if (isPlainObject(extPrebidTargeting)) { - // If wurl exists, remove hb_winurl and hb_bidid targeting attributes - if (isStr(deepAccess(bid, 'ext.prebid.events.win'))) { - extPrebidTargeting = getDefinedParams(extPrebidTargeting, Object.keys(extPrebidTargeting) - .filter(i => (i.indexOf('hb_winurl') === -1 && i.indexOf('hb_bidid') === -1))); - } - bidObject.adserverTargeting = extPrebidTargeting; - } - - bidObject.seatBidId = bid.id; - - if (deepAccess(bid, 'ext.prebid.type') === VIDEO) { - bidObject.mediaType = VIDEO; - const impReq = this.impRequested[bid.impid]; - [bidObject.playerWidth, bidObject.playerHeight] = [impReq.video.w, impReq.video.h]; - - // try to get cache values from 'response.ext.prebid.cache.js' - // else try 'bid.ext.prebid.targeting' as fallback - if (bid.ext.prebid.cache && typeof bid.ext.prebid.cache.vastXml === 'object' && bid.ext.prebid.cache.vastXml.cacheId && bid.ext.prebid.cache.vastXml.url) { - bidObject.videoCacheKey = bid.ext.prebid.cache.vastXml.cacheId; - bidObject.vastUrl = bid.ext.prebid.cache.vastXml.url; - } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) { - bidObject.videoCacheKey = extPrebidTargeting.hb_uuid; - // build url using key and cache host - bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`; - } - - if (bid.adm) { bidObject.vastXml = bid.adm; } - if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - } else if (FEATURES.NATIVE && deepAccess(bid, 'ext.prebid.type') === NATIVE) { - bidObject.mediaType = NATIVE; - let ortb; - if (typeof bid.adm === 'string') { - ortb = bidObject.adm = JSON.parse(bid.adm); - } else { - ortb = bidObject.adm = bid.adm; - } - - if (isPlainObject(ortb) && Array.isArray(ortb.assets)) { - bidObject.native = { - ortb, - }; - } else { - logError('prebid server native response contained no assets'); - } - } else { // banner - if (bid.adm && bid.nurl) { - bidObject.ad = bid.adm; - bidObject.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } else if (bid.adm) { - bidObject.ad = bid.adm; - } else if (bid.nurl) { - bidObject.adUrl = bid.nurl; - } - } - - bidObject.width = bid.w; - bidObject.height = bid.h; - if (bid.dealid) { bidObject.dealId = bid.dealid; } - bidObject.creative_id = bid.crid; - bidObject.creativeId = bid.crid; - if (bid.burl) { bidObject.burl = bid.burl; } - bidObject.currency = (response.cur) ? response.cur : DEFAULT_S2S_CURRENCY; - bidObject.meta = {}; - let extPrebidMeta = deepAccess(bid, 'ext.prebid.meta'); - if (extPrebidMeta && isPlainObject(extPrebidMeta)) { bidObject.meta = deepClone(extPrebidMeta); } - if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } - - // the OpenRTB location for "TTL" as understood by Prebid.js is "exp" (expiration). - const configTtl = s2sConfig.defaultTtl || DEFAULT_S2S_TTL; - bidObject.ttl = (bid.exp) ? bid.exp : configTtl; - bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; - - bids.push({ adUnit: this.adUnitsByImp[bid.impid].code, bid: bidObject }); - }); - }); - } - - return bids; - }, - setBidRequestId(impId, bidderCode, bidId) { - this.bidIdMap[this.impBidderKey(impId, bidderCode)] = bidId; - }, - getBidRequest(impId, bidderCode) { - const key = this.impBidderKey(impId, bidderCode); - return this.bidIdMap[key] && getBidRequest(this.bidIdMap[key], this.bidderRequests); - }, - impBidderKey(impId, bidderCode) { - return `${impId}${bidderCode}`; - } -}); - /** * BID_WON event to request the wurl * @param {Bid} bid the winning bid object @@ -1114,12 +435,13 @@ function getMatchingConsentUrl(urlProp, gdprConsent) { } function getConsentData(bidRequests) { - let gdprConsent, uspConsent; + let gdprConsent, uspConsent, gppConsent; if (Array.isArray(bidRequests) && bidRequests.length > 0) { gdprConsent = bidRequests[0].gdprConsent; uspConsent = bidRequests[0].uspConsent; + gppConsent = bidRequests[0].gppConsent; } - return { gdprConsent, uspConsent }; + return { gdprConsent, uspConsent, gppConsent }; } /** @@ -1153,7 +475,7 @@ export function PrebidServer() { done = adapterMetrics.startTiming('total').stopBefore(done); bidRequests.forEach(req => useMetrics(req.metrics).join(adapterMetrics, {continuePropagation: false})); - let { gdprConsent, uspConsent } = getConsentData(bidRequests); + let { gdprConsent, uspConsent, gppConsent } = getConsentData(bidRequests); if (Array.isArray(_s2sConfigs)) { if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { @@ -1162,7 +484,7 @@ export function PrebidServer() { .filter((bidder, index, array) => (array.indexOf(bidder) === index)); const storedRequests = getStoredRequestIds(bidRequests, s2sBidRequest); logInfo('S2S storedRequest ids for userSync', storedRequests); - queueSync(syncBidders, gdprConsent, uspConsent, s2sBidRequest.s2sConfig, storedRequests); + queueSync(syncBidders, gdprConsent, uspConsent, gppConsent, s2sBidRequest.s2sConfig, storedRequests); } processPBSRequest(s2sBidRequest, bidRequests, ajax, { @@ -1171,14 +493,24 @@ export function PrebidServer() { bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); } done(); - doClientSideSyncs(requestedBidders, gdprConsent, uspConsent); + doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, onError: done, onBid: function ({adUnit, bid}) { const metrics = bid.metrics = s2sBidRequest.metrics.fork().renameWith(); metrics.checkpoint('addBidResponse'); - if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnit, bid))) { - addBidResponse(adUnit, bid); + if ((bid.requestId == null || bid.requestBidder == null) && !s2sBidRequest.s2sConfig.allowUnknownBidderCodes) { + logWarn(`PBS adapter received bid from unknown bidder (${bid.bidder}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`); + addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.BIDDER_DISALLOWED); + } else { + if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnit, bid))) { + addBidResponse(adUnit, bid); + if (bid.pbsWurl) { + addWurl(bid.auctionId, bid.adId, bid.pbsWurl); + } + } else { + addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.INVALID); + } } } }) @@ -1215,8 +547,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques .reduce(flatten, []) .filter(uniques); - const ortb2 = new ORTB2(s2sBidRequest, bidRequests, adUnits, requestedBidders); - const request = s2sBidRequest.metrics.measureTime('buildRequests', () => ortb2.buildRequest()); + const request = s2sBidRequest.metrics.measureTime('buildRequests', () => buildPBSRequest(s2sBidRequest, bidRequests, adUnits, requestedBidders, eidPermissions)); const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); @@ -1230,7 +561,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let result; try { result = JSON.parse(response); - const bids = s2sBidRequest.metrics.measureTime('interpretResponse', () => ortb2.interpretResponse(result)); + const bids = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request).bids); bids.forEach(onBid); } catch (error) { logError(error); diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js new file mode 100644 index 00000000000..be034b3cfbe --- /dev/null +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -0,0 +1,281 @@ +import {ortbConverter} from '../../libraries/ortbConverter/converter.js'; +import { + deepAccess, + deepSetValue, + getBidRequest, + getDefinedParams, + isArray, + logError, + logWarn, + mergeDeep, + timestamp +} from '../../src/utils.js'; +import {config} from '../../src/config.js'; +import CONSTANTS from '../../src/constants.json'; +import {createBid} from '../../src/bidfactory.js'; +import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; +import {setImpBidParams} from '../../libraries/pbsExtensions/processors/params.js'; +import {SUPPORTED_MEDIA_TYPES} from '../../libraries/pbsExtensions/processors/mediaType.js'; +import {IMP, REQUEST, RESPONSE} from '../../src/pbjsORTB.js'; +import {beConvertCurrency} from '../../src/utils/currency.js'; + +const DEFAULT_S2S_TTL = 60; +const DEFAULT_S2S_CURRENCY = 'USD'; +const DEFAULT_S2S_NETREVENUE = true; +const BIDDER_SPECIFIC_REQUEST_PROPS = new Set(['bidderCode', 'bidderRequestId', 'uniquePbsTid', 'bids', 'timeout']); + +const PBS_CONVERTER = ortbConverter({ + processors: pbsExtensions, + context: { + netRevenue: DEFAULT_S2S_NETREVENUE, + }, + imp(buildImp, proxyBidRequest, context) { + Object.assign(context, proxyBidRequest.pbsData); + const imp = buildImp(proxyBidRequest, context); + (proxyBidRequest.bids || []).forEach(bid => { + if (bid.ortb2Imp && Object.keys(bid.ortb2Imp).length > 0) { + // set bidder-level imp attributes; see https://github.com/prebid/prebid-server/issues/2335 + deepSetValue(imp, `ext.prebid.imp.${bid.bidder}`, bid.ortb2Imp); + } + }); + if (Object.values(SUPPORTED_MEDIA_TYPES).some(mtype => imp[mtype])) { + imp.secure = context.s2sBidRequest.s2sConfig.secure; + return imp; + } + }, + request(buildRequest, imps, proxyBidderRequest, context) { + if (!imps.length) { + logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.'); + } else { + let {s2sBidRequest, requestedBidders, eidPermissions} = context; + const request = buildRequest(imps, proxyBidderRequest, context); + + request.tmax = s2sBidRequest.s2sConfig.timeout; + deepSetValue(request, 'source.tid', proxyBidderRequest.auctionId); + + [request.app, request.dooh, request.site].forEach(section => { + if (section && !section.publisher?.id) { + deepSetValue(section, 'publisher.id', s2sBidRequest.s2sConfig.accountId); + } + }) + + if (isArray(eidPermissions) && eidPermissions.length > 0) { + if (requestedBidders && isArray(requestedBidders)) { + eidPermissions = eidPermissions.map(p => ({ + ...p, + bidders: p.bidders.filter(bidder => requestedBidders.includes(bidder)) + })) + } + deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); + } + + return request; + } + }, + bidResponse(buildBidResponse, bid, context) { + // before sending the response throgh "stock" ortb conversion, here we need to: + // - filter out ones that come from an "unknown" bidder (if allowUnknownBidderCode is not set) + // - overwrite context.bidRequest with the actual bid request for this seat / imp combination + + let bidRequest = context.actualBidRequests.get(context.seatbid.seat); + if (bidRequest == null) { + // for stored impressions, a request was made with bidder code `null`. Pick it up here so that NO_BID, BID_WON, etc events + // can work as expected (otherwise, the original request will always result in NO_BID). + bidRequest = context.actualBidRequests.get(null); + } + + if (bidRequest) { + Object.assign(context, { + bidRequest, + bidderRequest: context.actualBidderRequests.find(req => req.bidderCode === bidRequest.bidder) + }) + } + + const bidResponse = buildBidResponse(bid, context); + bidResponse.requestBidder = bidRequest?.bidder; + + if (bidResponse.native?.ortb) { + // TODO: do we need to set bidResponse.adm here? + // Any consumers can now get the same object from bidResponse.native.ortb; + // I could not find any, which raises the question - who is looking for this? + bidResponse.adm = bidResponse.native.ortb; + } + + // because core has special treatment for PBS adapter responses, we need some additional processing + bidResponse.requestTimestamp = context.requestTimestamp; + const status = bid.price !== 0 ? CONSTANTS.STATUS.GOOD : CONSTANTS.STATUS.NO_BID; + return { + bid: Object.assign(createBid(status, { + src: CONSTANTS.S2S.SRC, + bidId: bidRequest ? (bidRequest.bidId || bidRequest.bid_Id) : null, + transactionId: context.adUnit.transactionId, + auctionId: context.bidderRequest.auctionId, + }), bidResponse), + adUnit: context.adUnit.code + }; + }, + overrides: { + [IMP]: { + id(orig, imp, proxyBidRequest, context) { + imp.id = context.impId; + }, + params(orig, imp, proxyBidRequest, context) { + // override params processing to do it for each bidRequest in this imp; + // also, take overrides from s2sConfig.adapterOptions + const adapterOptions = context.s2sBidRequest.s2sConfig.adapterOptions; + for (const req of context.actualBidRequests.values()) { + setImpBidParams(imp, req, context, context); + if (adapterOptions && adapterOptions[req.bidder]) { + Object.assign(imp.ext.prebid.bidder[req.bidder], adapterOptions[req.bidder]); + } + } + }, + bidfloor(orig, imp, proxyBidRequest, context) { + // for bid floors, we pass each bidRequest associated with this imp through normal bidfloor processing, + // and aggregate all of them into a single, minimum floor to put in the request + let min; + for (const req of context.actualBidRequests.values()) { + const floor = {}; + orig(floor, req, context); + // if any bid does not have a valid floor, do not attempt to send any to PBS + if (floor.bidfloorcur == null || floor.bidfloor == null) { + min = null; + break; + } else if (min == null) { + min = floor; + } else { + const value = beConvertCurrency(floor.bidfloor, floor.bidfloorcur, min.bidfloorcur); + if (value != null && value < min.bidfloor) { + min = floor; + } + } + } + if (min != null) { + Object.assign(imp, min); + } + } + }, + [REQUEST]: { + fpd(orig, ortbRequest, proxyBidderRequest, context) { + // FPD is handled different for PBS - the base request will only contain global FPD; + // bidder-specific values are set in ext.prebid.bidderconfig + + mergeDeep(ortbRequest, context.s2sBidRequest.ortb2Fragments?.global); + + // also merge in s2sConfig.extPrebid + if (context.s2sBidRequest.s2sConfig.extPrebid && typeof context.s2sBidRequest.s2sConfig.extPrebid === 'object') { + deepSetValue(ortbRequest, 'ext.prebid', mergeDeep(ortbRequest.ext?.prebid || {}, context.s2sBidRequest.s2sConfig.extPrebid)); + } + + const fpdConfigs = Object.entries(context.s2sBidRequest.ortb2Fragments?.bidder || {}).map(([bidder, ortb2]) => ({ + bidders: [bidder], + config: {ortb2} + })); + if (fpdConfigs.length) { + deepSetValue(ortbRequest, 'ext.prebid.bidderconfig', fpdConfigs); + } + }, + extPrebidAliases(orig, ortbRequest, proxyBidderRequest, context) { + // override alias processing to do it for each bidder in the request + context.actualBidderRequests.forEach(req => orig(ortbRequest, req, context)); + }, + sourceExtSchain(orig, ortbRequest, proxyBidderRequest, context) { + // pass schains in ext.prebid.schains, with the most commonly used one in source.ext.schain + let mainChain; + + let chains = (deepAccess(ortbRequest, 'ext.prebid.schains') || []); + const chainBidders = new Set(chains.flatMap((item) => item.bidders)); + + chains = Object.values( + chains + .concat(context.actualBidderRequests + .filter((req) => !chainBidders.has(req.bidderCode)) // schain defined in s2sConfig.extPrebid takes precedence + .map((req) => ({ + bidders: [req.bidderCode], + schain: deepAccess(req, 'bids.0.schain') + }))) + .filter(({bidders, schain}) => bidders?.length > 0 && schain) + .reduce((chains, {bidders, schain}) => { + const key = JSON.stringify(schain); + if (!chains.hasOwnProperty(key)) { + chains[key] = {bidders: new Set(), schain}; + } + bidders.forEach((bidder) => chains[key].bidders.add(bidder)); + if (mainChain == null || chains[key].bidders.size > mainChain.bidders.size) { + mainChain = chains[key] + } + return chains; + }, {}) + ).map(({bidders, schain}) => ({bidders: Array.from(bidders), schain})); + + if (mainChain != null) { + deepSetValue(ortbRequest, 'source.ext.schain', mainChain.schain); + } + + if (chains.length) { + deepSetValue(ortbRequest, 'ext.prebid.schains', chains); + } + } + }, + [RESPONSE]: { + serverSideStats(orig, response, ortbResponse, context) { + // override to process each request + context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids})); + } + } + }, +}); + +export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requestedBidders, eidPermissions) { + const requestTimestamp = timestamp(); + const impIds = new Set(); + const proxyBidRequests = []; + + adUnits.forEach(adUnit => { + const actualBidRequests = new Map(); + + adUnit.bids.forEach((bid) => { + if (bid.mediaTypes != null) { + // TODO: support labels / conditional bids + // for now, just warn about them + logWarn(`Prebid Server adapter does not (yet) support bidder-specific mediaTypes for the same adUnit. Size mapping configuration will be ignored for adUnit: ${adUnit.code}, bidder: ${bid.bidder}`); + } + actualBidRequests.set(bid.bidder, getBidRequest(bid.bid_id, bidderRequests)); + }); + + let impId = adUnit.code; + let i = 1; + while (impIds.has(impId)) { + i++; + impId = `${adUnit.code}-${i}`; + } + impIds.add(impId) + proxyBidRequests.push({ + ...adUnit, + adUnitCode: adUnit.code, + ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), + pbsData: {impId, actualBidRequests, adUnit} + }); + }); + + const proxyBidderRequest = Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))) + + return PBS_CONVERTER.toORTB({ + bidderRequest: proxyBidderRequest, + bidRequests: proxyBidRequests, + context: { + currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, + ttl: s2sBidRequest.s2sConfig.defaultTtl || DEFAULT_S2S_TTL, + requestTimestamp, + s2sBidRequest, + requestedBidders, + actualBidderRequests: bidderRequests, + eidPermissions, + nativeRequest: s2sBidRequest.s2sConfig.ortbNative, + } + }); +} + +export function interpretPBSResponse(response, request) { + return PBS_CONVERTER.fromORTB({response, request}); +} diff --git a/modules/priceFloors.js b/modules/priceFloors.js index e4203fd73d3..92aecb0ca50 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -10,6 +10,7 @@ import { logError, logInfo, logWarn, + mergeDeep, parseGPTSingleSizeArray, parseUrl, pick @@ -20,12 +21,14 @@ import {ajaxBuilder} from '../src/ajax.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {getHook} from '../src/hook.js'; -import {createBid} from '../src/bidfactory.js'; import {find} from '../src/polyfill.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {bidderSettings} from '../src/bidderSettings.js'; import {auctionManager} from '../src/auctionManager.js'; +import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; +import {beConvertCurrency} from '../src/utils/currency.js'; +import {adjustCpm} from '../src/utils/cpm.js'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -80,7 +83,7 @@ const getHostname = (() => { let domain; return function() { if (domain == null) { - domain = parseUrl(getRefererInfo().topmostLocation, {noDecodeWholeUrl: true}).hostname; + domain = parseUrl(getRefererInfo().topmostLocation, {noDecodeWholeURL: true}).hostname; } return domain; } @@ -177,12 +180,8 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) { /** * @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted */ -export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) { - const adjustmentFunction = bidderSettings.get(bidderName, 'bidCpmAdjustment'); - if (adjustmentFunction) { - return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm})); - } - return parseFloat(inputCpm); +export function getBiddersCpmAdjustment(inputCpm, bid, bidRequest) { + return parseFloat(adjustCpm(inputCpm, {...bid, cpm: inputCpm}, bidRequest)); } /** @@ -247,8 +246,14 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: // if cpmAdjustment flag is true and we have a valid floor then run the adjustment on it if (floorData.enforcement.bidAdjustment && floorInfo.matchingFloor) { - let cpmAdjustment = getBiddersCpmAdjustment(bidRequest.bidder, floorInfo.matchingFloor); - floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + // pub provided inverse function takes precedence, otherwise do old adjustment stuff + const inverseFunction = bidderSettings.get(bidRequest.bidder, 'inverseBidAdjustment'); + if (inverseFunction) { + floorInfo.matchingFloor = inverseFunction(floorInfo.matchingFloor, bidRequest); + } else { + let cpmAdjustment = getBiddersCpmAdjustment(floorInfo.matchingFloor, null, bidRequest); + floorInfo.matchingFloor = cpmAdjustment ? calculateAdjustedFloor(floorInfo.matchingFloor, cpmAdjustment) : floorInfo.matchingFloor; + } } if (floorInfo.matchingFloor) { @@ -305,6 +310,8 @@ export function getFloorDataFromAdUnits(adUnits) { // copy over the new rules into our values object Object.assign(accum.values, newRules); } + } else if (adUnit.floors != null) { + logWarn(`adUnit '${adUnit.code}' provides an invalid \`floor\` definition, it will be ignored for floor calculations`, adUnit); } return accum; }, {}); @@ -694,11 +701,11 @@ function shouldFloorBid(floorData, floorInfo, bid) { * @summary The main driving force of floors. On bidResponse we hook in and intercept bidResponses. * And if the rule we find determines a bid should be floored we will do so. */ -export const addBidResponseHook = timedBidResponseHook('priceFloors', function addBidResponseHook(fn, adUnitCode, bid) { +export const addBidResponseHook = timedBidResponseHook('priceFloors', function addBidResponseHook(fn, adUnitCode, bid, reject) { let floorData = _floorDataForAuction[bid.auctionId]; // if no floor data then bail if (!floorData || !bid || floorData.skipped) { - return fn.call(this, adUnitCode, bid); + return fn.call(this, adUnitCode, bid, reject); } const matchingBidRequest = auctionManager.index.getBidRequest(bid); @@ -708,7 +715,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a if (!floorInfo.matchingFloor) { logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid); - return fn.call(this, adUnitCode, bid); + return fn.call(this, adUnitCode, bid, reject); } // determine the base cpm to use based on if the currency matches the floor currency @@ -724,12 +731,12 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a adjustedCpm = getGlobal().convertCurrency(bid.cpm, bidResponseCurrency.toUpperCase(), floorCurrency); } catch (err) { logError(`${MODULE_NAME}: Unable do get currency conversion for bidResponse to Floor Currency. Do you have Currency module enabled? ${bid}`); - return fn.call(this, adUnitCode, bid); + return fn.call(this, adUnitCode, bid, reject); } } // ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists - adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid); + adjustedCpm = getBiddersCpmAdjustment(adjustedCpm, bid, matchingBidRequest); // add necessary data information for analytics adapters / floor providers would possibly need addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm); @@ -737,25 +744,81 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a // now do the compare! if (shouldFloorBid(floorData, floorInfo, bid)) { // bid fails floor -> throw it out - // create basic bid no-bid with necessary data fro analytics adapters - let flooredBid = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers()); - Object.assign(flooredBid, pick(bid, [ - 'floorData', - 'width', - 'height', - 'mediaType', - 'currency', - 'originalCpm', - 'originalCurrency', - 'getCpmInNewCurrency', - ])); - flooredBid.status = CONSTANTS.BID_STATUS.BID_REJECTED; - // if floor not met update bid with 0 cpm so it is not included downstream and marked as no-bid - flooredBid.cpm = 0; + // continue with a "NO_BID" bid, TODO: remove this in v8 + const flooredBid = reject(CONSTANTS.REJECTION_REASON.FLOOR_NOT_MET); logWarn(`${MODULE_NAME}: ${flooredBid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met (adjusted cpm: ${bid?.floorData?.cpmAfterAdjustments}, floor: ${floorInfo?.matchingFloor})`, bid); - return fn.call(this, adUnitCode, flooredBid); + return fn.call(this, adUnitCode, flooredBid, reject); } - return fn.call(this, adUnitCode, bid); + return fn.call(this, adUnitCode, bid, reject); }); config.getConfig('floors', config => handleSetFloorsConfig(config.floors)); + +/** + * Sets bidfloor and bidfloorcur for ORTB imp objects + */ +export function setOrtbImpBidFloor(imp, bidRequest, context) { + if (typeof bidRequest.getFloor === 'function') { + let currency, floor; + try { + ({currency, floor} = bidRequest.getFloor({ + currency: context.currency || config.getConfig('currency.adServerCurrency') || 'USD', + mediaType: context.mediaType || '*', + size: '*' + })); + } catch (e) { + logWarn('Cannot compute floor for bid', bidRequest); + return; + } + floor = parseFloat(floor); + if (currency != null && floor != null && !isNaN(floor)) { + Object.assign(imp, { + bidfloor: floor, + bidfloorcur: currency + }); + } + } +} + +export function setImpExtPrebidFloors(imp, bidRequest, context) { + // logic below relates to https://github.com/prebid/Prebid.js/issues/8749 and does the following: + // 1. check client-side floors (ref bidfloor/bidfloorcur & ortb2Imp floorMin/floorMinCur (if present)) + // 2. set pbs req wide floorMinCur to the first floor currency found when iterating over imp's + // (if currency conversion logic present, convert all imp floor values to this currency) + // 3. compare/store ref to lowest floorMin value as each imp is iterated over + // 4. set req wide floorMin and floorMinCur values for pbs after iterations are done + + if (imp.bidfloor != null) { + let {floorMinCur, floorMin} = context.reqContext.floorMin || {}; + + if (floorMinCur == null) { floorMinCur = imp.bidfloorcur } + const ortb2ImpFloorCur = imp.ext?.prebid?.floors?.floorMinCur || imp.ext?.prebid?.floorMinCur || floorMinCur; + const ortb2ImpFloorMin = imp.ext?.prebid?.floors?.floorMin || imp.ext?.prebid?.floorMin; + const convertedFloorMinValue = beConvertCurrency(imp.bidfloor, imp.bidfloorcur, floorMinCur); + const convertedOrtb2ImpFloorMinValue = ortb2ImpFloorMin && ortb2ImpFloorCur ? beConvertCurrency(ortb2ImpFloorMin, ortb2ImpFloorCur, floorMinCur) : false; + + const lowestImpFloorMin = convertedOrtb2ImpFloorMinValue && convertedOrtb2ImpFloorMinValue < convertedFloorMinValue + ? convertedOrtb2ImpFloorMinValue + : convertedFloorMinValue; + + deepSetValue(imp, 'ext.prebid.floors.floorMin', lowestImpFloorMin); + if (floorMin == null || floorMin > lowestImpFloorMin) { floorMin = lowestImpFloorMin } + context.reqContext.floorMin = {floorMin, floorMinCur}; + } +} + +/** + * PBS specific extension: set ext.prebid.floors.enabled = false if floors are processed client-side + */ +export function setOrtbExtPrebidFloors(ortbRequest, bidderRequest, context) { + if (addedFloorsHook) { + deepSetValue(ortbRequest, 'ext.prebid.floors.enabled', ortbRequest.ext?.prebid?.floors?.enabled || false); + } + if (context?.floorMin) { + mergeDeep(ortbRequest, {ext: {prebid: {floors: context.floorMin}}}) + } +} + +registerOrtbProcessor({type: IMP, name: 'bidfloor', fn: setOrtbImpBidFloor}); +registerOrtbProcessor({type: IMP, name: 'extPrebidFloors', fn: setImpExtPrebidFloors, dialects: [PBS], priority: -1}); +registerOrtbProcessor({type: REQUEST, name: 'extPrebidFloors', fn: setOrtbExtPrebidFloors, dialects: [PBS]}); diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 04d363be7fb..1f113f9c432 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -3,9 +3,9 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'proxistore'; const PROXISTORE_VENDOR_ID = 418; -const COOKIE_BASE_URL = 'https://abs.proxistore.com/v3/rtb/prebid/multi'; +const COOKIE_BASE_URL = 'https://api.proxistore.com/v3/rtb/prebid/multi'; const COOKIE_LESS_URL = - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi'; + 'https://api.cookieless-proxistore.com/v3/rtb/prebid/multi'; function _createServerRequest(bidRequests, bidderRequest) { var sizeIds = []; diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index 5cadd522af2..1fde8f8db5b 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -9,8 +9,9 @@ import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; import {timedAuctionHook} from '../src/utils/perfMetrics.js'; +import {VENDORLESS_GVLID} from '../src/consentHandler.js'; -const storage = getStorageManager({moduleName: 'pubCommonId'}); +const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); const ID_NAME = '_pubcid'; const OPTOUT_NAME = '_pubcid_optout'; diff --git a/modules/pubProvidedIdSystem.js b/modules/pubProvidedIdSystem.js index 669d223c57f..baffd997443 100644 --- a/modules/pubProvidedIdSystem.js +++ b/modules/pubProvidedIdSystem.js @@ -7,6 +7,7 @@ import {submodule} from '../src/hook.js'; import { logInfo, isArray } from '../src/utils.js'; +import {VENDORLESS_GVLID} from '../src/consentHandler.js'; const MODULE_NAME = 'pubProvidedId'; @@ -18,6 +19,7 @@ export const pubProvidedIdSubmodule = { * @type {string} */ name: MODULE_NAME, + gvlid: VENDORLESS_GVLID, /** * decode the stored id value for passing to bid request diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index c8dc7cef15d..cae94f6fe7b 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -30,6 +30,11 @@ const DEFAULT_PUBLISHER_ID = 0; const DEFAULT_PROFILE_ID = 0; const DEFAULT_PROFILE_VERSION_ID = 0; const enc = window.encodeURIComponent; +const MEDIATYPE = { + BANNER: 0, + VIDEO: 1, + NATIVE: 2 +} /// /////////// VARIABLES ////////////// let publisherId = DEFAULT_PUBLISHER_ID; // int: mandatory @@ -89,6 +94,7 @@ function copyRequiredBidDetails(bid) { 'status', () => NO_BID, // default a bid to NO_BID until response is recieved or bid is timed out 'finalSource as source', 'params', + 'floorData', 'adUnit', () => pick(bid, [ 'adUnitCode', 'transactionId', @@ -153,6 +159,7 @@ function parseBidResponse(bid) { 'bidId', 'mediaType', 'params', + 'floorData', 'mi', 'regexPattern', () => bid.regexPattern || undefined, 'partnerImpId', // partner impression ID @@ -243,17 +250,28 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'af': bid.bidResponse ? (bid.bidResponse.mediaType || undefined) : undefined, 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, - 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING + 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING, + 'frv': (s2sBidders.indexOf(bid.bidder) > -1) ? undefined : (bid.bidResponse ? (bid.bidResponse.floorData ? bid.bidResponse.floorData.floorRuleValue : undefined) : undefined) }); }); return partnerBids; }, []) } +function getAdUnitAdFormats(adUnit) { + var af = adUnit ? Object.keys(adUnit.mediaTypes || {}).map(format => MEDIATYPE[format.toUpperCase()]) : []; + return af; +} + +function getAdUnit(adUnits, adUnitId) { + return adUnits.filter(adUnit => (adUnit.divID && adUnit.divID == adUnitId) || (adUnit.code == adUnitId))[0]; +} + function executeBidsLoggerCall(e, highestCpmBids) { let auctionId = e.auctionId; let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; let auctionCache = cache.auctions[auctionId]; + let floorData = auctionCache.floorData; let outputObj = { s: [] }; let pixelURL = END_POINT_BID_LOGGER; @@ -283,12 +301,21 @@ function executeBidsLoggerCall(e, highestCpmBids) { return 0; })(); + if (floorData) { + outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; + outputObj['ft'] = floorData.floorResponseData ? (floorData.floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; + } + outputObj.s = Object.keys(auctionCache.adUnitCodes).reduce(function(slotsArray, adUnitId) { let adUnit = auctionCache.adUnitCodes[adUnitId]; + let origAdUnit = getAdUnit(auctionCache.origAdUnits, adUnitId) || {}; let slotObject = { 'sn': adUnitId, + 'au': origAdUnit.adUnitId || adUnitId, + 'mt': getAdUnitAdFormats(origAdUnit), 'sz': adUnit.dimensions.map(e => e[0] + 'x' + e[1]), - 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)) + 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), + 'fskp': floorData ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, }; slotsArray.push(slotObject); return slotsArray; @@ -318,6 +345,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { } const adapterName = getAdapterNameForAlias(winningBid.adapterCode || winningBid.bidder); + let origAdUnit = getAdUnit(cache.auctions[auctionId].origAdUnits, adUnitId) || {}; let pixelURL = END_POINT_WIN_BID_LOGGER; pixelURL += 'pubid=' + publisherId; pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''); @@ -327,6 +355,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&pid=' + enc(profileId); pixelURL += '&pdvid=' + enc(profileVersionId); pixelURL += '&slot=' + enc(adUnitId); + pixelURL += '&au=' + enc(origAdUnit.adUnitId || adUnitId); pixelURL += '&pn=' + enc(adapterName); pixelURL += '&bc=' + enc(winningBid.bidderCode || winningBid.bidder); pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); @@ -358,6 +387,8 @@ function auctionInitHandler(args) { 'bidderDonePendingCount', () => args.bidderRequests.length, ]); cacheEntry.adUnitCodes = {}; + cacheEntry.floorData = {}; + cacheEntry.origAdUnits = args.adUnits; cacheEntry.referer = args.bidderRequests[0].refererInfo.topmostLocation; cache.auctions[args.auctionId] = cacheEntry; } @@ -372,6 +403,9 @@ function bidRequestedHandler(args) { }; } cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId] = [copyRequiredBidDetails(bid)]; + if (bid.floorData) { + cache.auctions[args.auctionId].floorData['floorRequestData'] = bid.floorData; + } }) } @@ -386,6 +420,11 @@ function bidResponseHandler(args) { bid = copyRequiredBidDetails(args); cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId].push(bid); } + + if (args.floorData) { + cache.auctions[args.auctionId].floorData['floorResponseData'] = args.floorData; + } + bid.adId = args.adId; bid.source = formatSource(bid.source || args.source); setBidStatus(bid, args); diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 7f346b1c7b0..d8f5002ebd7 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,7 +1,7 @@ -import { logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques } from '../src/utils.js'; +import { getBidRequest, logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -392,37 +392,29 @@ function _createNativeRequest(params) { } break; case NATIVE_ASSETS.IMAGE.KEY: - if (params[key].sizes && params[key].sizes.length > 0) { - assetObj = { - id: NATIVE_ASSETS.IMAGE.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : UNDEFINED), - hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : UNDEFINED), - mimes: params[key].mimes, - ext: params[key].ext, - } - }; - } else { - logWarn(LOG_WARN_PREFIX + 'Error: Image sizes is required for native ad: ' + JSON.stringify(params)); - } + assetObj = { + id: NATIVE_ASSETS.IMAGE.ID, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.IMAGE, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), + wmin: params[key].wmin || params[key].minimumWidth || (params[key].minsizes ? params[key].minsizes[0] : UNDEFINED), + hmin: params[key].hmin || params[key].minimumHeight || (params[key].minsizes ? params[key].minsizes[1] : UNDEFINED), + mimes: params[key].mimes, + ext: params[key].ext, + } + }; break; case NATIVE_ASSETS.ICON.KEY: - if (params[key].sizes && params[key].sizes.length > 0) { - assetObj = { - id: NATIVE_ASSETS.ICON.ID, - required: params[key].required ? 1 : 0, - img: { - type: NATIVE_ASSET_IMAGE_TYPE.ICON, - w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), - h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), - } - }; - } else { - logWarn(LOG_WARN_PREFIX + 'Error: Icon sizes is required for native ad: ' + JSON.stringify(params)); + assetObj = { + id: NATIVE_ASSETS.ICON.ID, + required: params[key].required ? 1 : 0, + img: { + type: NATIVE_ASSET_IMAGE_TYPE.ICON, + w: params[key].w || params[key].width || (params[key].sizes ? params[key].sizes[0] : UNDEFINED), + h: params[key].h || params[key].height || (params[key].sizes ? params[key].sizes[1] : UNDEFINED), + } }; break; case NATIVE_ASSETS.VIDEO.KEY: @@ -953,6 +945,29 @@ function _assignRenderer(newBid, request) { } } +/** + * In case of adpod video context, assign prebiddealpriority to the dealtier property of adpod-video bid, + * so that adpod module can set the hb_pb_cat_dur targetting key. + * @param {*} newBid + * @param {*} bid + * @param {*} request + * @returns + */ +export function assignDealTier(newBid, bid, request) { + if (!bid?.ext?.prebiddealpriority) return; + const bidRequest = getBidRequest(newBid.requestId, [request.bidderRequest]); + const videoObj = deepAccess(bidRequest, 'mediaTypes.video'); + if (videoObj?.context != ADPOD) return; + + const duration = bid?.ext?.video?.duration || videoObj?.maxduration; + // if (!duration) return; + newBid.video = { + context: ADPOD, + durationSeconds: duration, + dealTier: bid.ext.prebiddealpriority + }; +} + function isNonEmptyArray(test) { if (isArray(test) === true) { if (test.length > 0) { @@ -1174,6 +1189,10 @@ export const spec = { if (commonFpd.bcat) { blockedIabCategories = blockedIabCategories.concat(commonFpd.bcat); } + // check if fpd ortb2 contains device property with sua object + if (commonFpd.device?.sua) { + payload.device.sua = commonFpd.device?.sua; + } if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) { const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat; @@ -1239,7 +1258,7 @@ export const spec = { seatbidder.bid.forEach(bid => { let newBid = { requestId: bid.impid, - cpm: (parseFloat(bid.price) || 0).toFixed(2), + cpm: parseFloat((bid.price || 0).toFixed(2)), width: bid.w, height: bid.h, creativeId: bid.crid || bid.id, @@ -1265,6 +1284,7 @@ export const spec = { newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h; newBid.vastXml = bid.adm; _assignRenderer(newBid, request); + assignDealTier(newBid, bid, request); break; case NATIVE: _parseNativeResponse(bid, newBid); diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index 7721fe10459..5e381e74a18 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -2,8 +2,7 @@ import { _each, isStr, deepClone, isArray, deepSetValue, inIframe, logMessage, l import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const VERSION = '0.1.0'; +const VERSION = '0.2.0'; const GVLID = 842; const NET_REVENUE = true; const UNDEFINED = undefined; @@ -117,9 +116,6 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -170,7 +166,7 @@ export const spec = { payload.user.geo.lon = _parseSlotParam('lon', conf.lon); payload.user.yob = _parseSlotParam('yob', conf.yob); payload.device.geo = payload.user.geo; - payload.site.page = payload.site.page.trim(); + payload.site.page = payload.site?.page?.trim(); payload.site.domain = _getDomainFromURL(payload.site.page); // add the content object from config in request @@ -184,7 +180,7 @@ export const spec = { } // passing transactionId in source.tid - deepSetValue(payload, 'source.tid', conf.transactionId); + deepSetValue(payload, 'source.tid', bidderRequest?.auctionId); // schain if (validBidRequests[0].schain) { @@ -440,7 +436,10 @@ function _createImpressionObject(bid, conf) { tagid: bid.params.adUnit || undefined, bidfloor: _parseSlotParam('bidFloor', bid.params.bidFloor), // capitalization dicated by 3.2.4 spec secure: 1, - bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY // capitalization dicated by 3.2.4 spec + bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY, // capitalization dicated by 3.2.4 spec + ext: { + tid: (bid.transactionId ? bid.transactionId : '') + } }; if (bid.hasOwnProperty('mediaTypes')) { @@ -494,7 +493,11 @@ function _parseSlotParam(paramName, paramValue) { function _parseAdSlot(bid) { _logInfo('parseAdSlot bid', bid) - bid.params.adUnit = ''; + if (bid.adUnitCode) { + bid.params.adUnit = bid.adUnitCode; + } else { + bid.params.adUnit = ''; + } bid.params.width = 0; bid.params.height = 0; bid.params.adSlot = _cleanSlotName(bid.params.adSlot); @@ -532,9 +535,8 @@ function _cleanSlotName(slotName) { function _initConf(refererInfo) { return { - // TODO: do the fallbacks make sense here? - pageURL: refererInfo?.page || window.location.href, - refURL: refererInfo?.ref || window.document.referrer + pageURL: refererInfo?.page, + refURL: refererInfo?.ref }; } diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index f7e73dd5fdd..96665c786bf 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -6,7 +6,7 @@ import CONSTANTS from '../src/constants.json'; const emptyUrl = ''; const analyticsType = 'endpoint'; -const pubxaiAnalyticsVersion = 'v1.1.0'; +const pubxaiAnalyticsVersion = 'v1.2.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; @@ -154,7 +154,7 @@ function send(data, status) { search: location.search }); if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - data.pageDetail.adUnitCount = data.auctionInit.adUnitCodes ? data.auctionInit.adUnitCodes.length : null; + data.pageDetail.adUnits = data.auctionInit.adUnitCodes; data.initOptions.auctionId = data.auctionInit.auctionId; delete data.auctionInit; diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 52931be0078..015e80d5692 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -16,6 +16,7 @@ const DEFAULT_BID_TTL = 20; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'video', 'battr', 'bcat', 'badv', 'bidfloor']; +const DEFAULT_TMAX = 500; /** * PulsePoint Bid Adapter. @@ -54,6 +55,7 @@ export const spec = { user: user(bidRequests[0], bidderRequest), regs: regs(bidderRequest), source: source(bidRequests[0].schain), + tmax: bidderRequest.timeout || DEFAULT_TMAX, }; return { method: 'POST', @@ -153,6 +155,8 @@ function bidResponseAvailable(request, response) { * Produces an OpenRTBImpression from a slot config. */ function impression(slot) { + var firstPartyData = slot.ortb2Imp?.ext || {}; + var ext = Object.assign({}, firstPartyData, slotUnknownParams(slot)); return { id: slot.bidId, banner: banner(slot), @@ -160,7 +164,7 @@ function impression(slot) { tagid: slot.params.ct.toString(), video: video(slot), bidfloor: bidFloor(slot), - ext: ext(slot), + ext: Object.keys(ext).length > 0 ? ext : null, }; } @@ -209,7 +213,7 @@ function video(slot) { /** * Unknown params are captured and sent on ext */ -function ext(slot) { +function slotUnknownParams(slot) { const ext = {}; const knownParamsMap = {}; KNOWN_PARAMS.forEach(value => knownParamsMap[value] = 1); @@ -330,14 +334,16 @@ function site(bidRequests, bidderRequest) { const pubId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.cp : '0'; const appParams = bidRequests[0].params.app; if (!appParams) { - return { + // use the first party data if available, and override only publisher/ref/page properties + var firstPartyData = bidderRequest?.ortb2?.site || {}; + return Object.assign({}, firstPartyData, { publisher: { id: pubId.toString(), }, // TODO: does the fallback make sense here? ref: bidderRequest?.refererInfo?.ref || window.document.referrer, page: bidderRequest?.refererInfo?.page || '' - } + }); } return null; } @@ -406,7 +412,8 @@ function adSize(slot, sizes) { * an openrtb User object. */ function user(bidRequest, bidderRequest) { - var ext = {}; + var user = bidderRequest?.ortb2?.user || { ext: {} }; + var ext = user.ext; if (bidderRequest) { if (bidderRequest.gdprConsent) { ext.consent = bidderRequest.gdprConsent.consentString; @@ -418,7 +425,7 @@ function user(bidRequest, bidderRequest) { ext.eids = eids; } } - return { ext }; + return user; } /** diff --git a/modules/rasBidAdapter.js b/modules/rasBidAdapter.js index 7bc3cf66b0d..f8ca260d490 100644 --- a/modules/rasBidAdapter.js +++ b/modules/rasBidAdapter.js @@ -1,4 +1,3 @@ -import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { isEmpty, getAdUnitSizes, parseSizesInput, deepAccess } from '../src/utils.js'; @@ -12,9 +11,15 @@ const getEndpoint = (network) => { function parseParams(params, bidderRequest) { const newParams = {}; + if (params.customParams && typeof params.customParams === 'object') { + for (const param in params.customParams) { + if (params.customParams.hasOwnProperty(param)) { + newParams[param] = params.customParams[param]; + } + } + } const du = deepAccess(bidderRequest, 'refererInfo.page'); const dr = deepAccess(bidderRequest, 'refererInfo.ref'); - if (du) { newParams.du = du; } @@ -93,7 +98,7 @@ const getSlots = (bidRequests) => { const batchSize = bidRequests.length; for (let i = 0; i < batchSize; i++) { const adunit = bidRequests[i]; - const slotSequence = utils.deepAccess(adunit, 'params.slotSequence'); + const slotSequence = deepAccess(adunit, 'params.slotSequence'); const sizes = parseSizesInput(getAdUnitSizes(adunit)).join(','); diff --git a/modules/rasBidAdapter.md b/modules/rasBidAdapter.md index 384ba0b611f..e8a61974130 100644 --- a/modules/rasBidAdapter.md +++ b/modules/rasBidAdapter.md @@ -34,18 +34,19 @@ var adUnits = [{ # Parameters -| Name | Scope | Type | Description | Example -| --- | --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- -| network | required | String | Specific identifier provided by RAS | `"4178463"` -| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by RAS | `"example_com"` -| area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed | `"sport"` -| slot | required | String | Ad unit placement name (case-insensitive) provided by RAS | `"slot"` -| slotSequence | optional | Number | Ad unit sequence position provided by RAS | `1` -| pageContext | optional | Object | Web page context data | `{}` -| pageContext.dr | optional | String | Document referrer URL address | `"https://example.com/"` -| pageContext.du | optional | String | Document URL address | `"https://example.com/sport/football/article.html?id=932016a5-02fc-4d5c-b643-fafc2f270f06"` -| pageContext.dv | optional | String | Document virtual address as slash-separated path that may consist of any number of parts (case-insensitive alphanumeric with underscores and hyphens); first part should be the same as `site` value and second as `area` value; next parts may reflect website navigation | `"example_com/sport/football"` -| pageContext.keyWords | optional | String[] | List of keywords associated with this ad unit; only case-insensitive alphanumeric with underscores and hyphens are allowed | `["euro", "lewandowski"]` -| pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}` -| pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"` -| pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"` +| Name | Scope | Type | Description | Example | +|------------------------------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| network | required | String | Specific identifier provided by RAS | `"4178463"` | +| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by RAS | `"example_com"` | +| area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed | `"sport"` | +| slot | required | String | Ad unit placement name (case-insensitive) provided by RAS | `"slot"` | +| slotSequence | optional | Number | Ad unit sequence position provided by RAS | `1` | +| pageContext | optional | Object | Web page context data | `{}` | +| pageContext.dr | optional | String | Document referrer URL address | `"https://example.com/"` | +| pageContext.du | optional | String | Document URL address | `"https://example.com/sport/football/article.html?id=932016a5-02fc-4d5c-b643-fafc2f270f06"` | +| pageContext.dv | optional | String | Document virtual address as slash-separated path that may consist of any number of parts (case-insensitive alphanumeric with underscores and hyphens); first part should be the same as `site` value and second as `area` value; next parts may reflect website navigation | `"example_com/sport/football"` | +| pageContext.keyWords | optional | String[] | List of keywords associated with this ad unit; only case-insensitive alphanumeric with underscores and hyphens are allowed | `["euro", "lewandowski"]` | +| pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}` | +| pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"` | +| pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"` | +| customParams | optional | Object | Custom request params | `{}` | \ No newline at end of file diff --git a/modules/redtramBidAdapter.js b/modules/redtramBidAdapter.js new file mode 100644 index 00000000000..e1dc0e2a148 --- /dev/null +++ b/modules/redtramBidAdapter.js @@ -0,0 +1,155 @@ +import { + isFn, + isStr, + deepAccess, + getWindowTop, + triggerPixel +} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'redtram'; +const AD_URL = 'https://prebid.redtram.com/pbjs'; +const SYNC_URL = 'https://prebid.redtram.com/sync'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency || !bid.meta) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + default: + return false; + } +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidFloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.placementId); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const winTop = getWindowTop(); + const location = winTop.location; + const placements = []; + + const request = { + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + host: location.host, + page: location.pathname, + placements: placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent; + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + bidfloor: getBidFloor(bid) + }; + + if (typeof bid.userId !== 'undefined') { + placement.userId = bid.userId; + } + + const mediaType = bid.mediaTypes; + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.adFormat = BANNER; + } + + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + }, + + onBidWon: (bid) => { + const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; + if (isStr(bid.nurl) && bid.nurl !== '') { + bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); + triggerPixel(bid.nurl); + } + } +}; + +registerBidder(spec); diff --git a/modules/redtramBidAdapter.md b/modules/redtramBidAdapter.md new file mode 100644 index 00000000000..39115502aa7 --- /dev/null +++ b/modules/redtramBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: redtram Bidder Adapter +Module Type: redtram Bidder Adapter +Maintainer: support@redtram.com +``` + +# Description + +Module that connects to redtram demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'div-prebid', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'redtram', + params: { + placementId: '23611' //test, please replace after test + } + } + ] + }, + ]; +``` \ No newline at end of file diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index c1360b79066..0c4d6148f2b 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -309,6 +309,16 @@ function generateBidParameters(bid, bidderRequest) { bidObject.placementId = placementId; } + const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + + const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); + if (api) { + bidObject.api = api; + } + if (mediaType === VIDEO) { const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); let playbackMethodValue; @@ -349,6 +359,11 @@ function generateBidParameters(bid, bidderRequest) { if (linearity) { bidObject.linearity = linearity; } + + const protocols = deepAccess(bid, `mediaTypes.video.protocols`); + if (protocols) { + bidObject.protocols = protocols; + } } return bidObject; @@ -385,7 +400,8 @@ function generateGeneralParams(generalObject, bidderRequest) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, device_type: getDeviceType(navigator.userAgent), ua: navigator.userAgent, - session_id: getBidIdParameter('auctionId', generalObject), + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('auctionId', generalObject), tmax: timeout }; diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 2920fd28528..f0837cb5508 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -20,11 +20,13 @@ The adapter supports Video(instream). | Name | Scope | Type | Description | Example | ---- | ----- | ---- | ----------- | ------- -| `org` | required | String | Rise publisher Id provided by your Rise representative | "56f91cd4d3e3660002000033" +| `org` | required | String | Rise publisher Id provided by your Rise representative | "1234567890abcdef12345678" | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false | `rtbDomain` | optional | String | Sets the seller end point | "www.test.com" +| `is_wrapper` | private | Boolean | Please don't use unless your account manager asked you to | false + # Test Parameters ```javascript @@ -41,10 +43,10 @@ var adUnits = [ bids: [{ bidder: 'rise', params: { - org: '56f91cd4d3e3660002000033', // Required + org: '1234567890abcdef12345678', // Required floorPrice: 2.00, // Optional placementId: '12345678', // Optional - testMode: false // Optional, + testMode: false, // Optional, rtbDomain: "www.test.com" //Optional } }] diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index fdf64483da7..9f498014a8e 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,13 +1,15 @@ -import {deepAccess, isArray, logError} from '../src/utils.js'; +import {deepAccess, mergeDeep, isArray, logError, logInfo} from '../src/utils.js'; import { getOrigin } from '../libraries/getOrigin/index.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {includes} from '../src/polyfill.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; const ENDPOINT_URL = 'creativecdn.com/bidder/prebid/bids'; +const FLEDGE_ENDPOINT_URL = 'creativecdn.com/bidder/prebidfledge/bids'; const DEFAULT_CURRENCY_ARR = ['USD']; // NOTE - USD is the only supported currency right now; Hardcoded for bids const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE]; const TTL = 55; @@ -50,12 +52,13 @@ export const spec = { const request = { id: validBidRequests[0].auctionId, - imp: validBidRequests.map(slot => mapImpression(slot)), + imp: validBidRequests.map(slot => mapImpression(slot, bidderRequest)), site: mapSite(validBidRequests, bidderRequest), cur: DEFAULT_CURRENCY_ARR, test: validBidRequests[0].params.test || 0, source: mapSource(validBidRequests[0]), }; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { const consentStr = (bidderRequest.gdprConsent.consentString) ? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : ''; @@ -81,37 +84,32 @@ export const spec = { } } - const ortb2Params = bidderRequest && bidderRequest.ortb2; - if (ortb2Params?.user) { - request.user = { - ...request.user, - ...(ortb2Params.user.data && { - data: { ...request.user?.data, ...ortb2Params.user.data }, - }), - ...(ortb2Params.user.ext && { - ext: { ...request.user?.ext, ...ortb2Params.user.ext }, - }), - }; + const ortb2Params = bidderRequest?.ortb2 || {}; + if (ortb2Params.site) { + mergeDeep(request, { site: ortb2Params.site }); + } + if (ortb2Params.user) { + mergeDeep(request, { user: ortb2Params.user }); + } + if (ortb2Params.device) { + mergeDeep(request, { device: ortb2Params.device }); } - if (ortb2Params?.site) { - request.site = { - ...request.site, - ...(ortb2Params.site.content && { - content: { ...request.site?.content, ...ortb2Params.site.content }, - }), - ...(ortb2Params.site.ext && { - ext: { ...request.site?.ext, ...ortb2Params.site.ext }, - }), - }; + + let computedEndpointUrl = ENDPOINT_URL; + + const fledgeConfig = config.getConfig('fledgeConfig'); + if (bidderRequest.fledgeEnabled && fledgeConfig) { + mergeDeep(request, { ext: { fledge_config: fledgeConfig } }); + computedEndpointUrl = FLEDGE_ENDPOINT_URL; } return { method: 'POST', - url: 'https://' + validBidRequests[0].params.region + '.' + ENDPOINT_URL, + url: 'https://' + validBidRequests[0].params.region + '.' + computedEndpointUrl, data: JSON.stringify(request) }; }, - interpretResponse: function (serverResponse, originalRequest) { + interpretOrtbResponse: function (serverResponse, originalRequest) { const responseBody = serverResponse.body; if (!isArray(responseBody)) { return []; @@ -119,17 +117,72 @@ export const spec = { const bids = []; responseBody.forEach(serverBid => { - if (serverBid.price === 0) { + if (!serverBid.price) { // price may exist and is === 0 or there's no price prop at all (fledge req case) return; } + + let interpretedBid; + // try...catch would be risky cause JSON.parse throws SyntaxError if (serverBid.adm.indexOf('{') === 0) { - bids.push(interpretNativeBid(serverBid)); + interpretedBid = interpretNativeBid(serverBid); } else { - bids.push(interpretBannerBid(serverBid)); + interpretedBid = interpretBannerBid(serverBid); } + if (serverBid.ext) interpretedBid.ext = serverBid.ext; + + bids.push(interpretedBid); }); return bids; + }, + interpretResponse: function (serverResponse, originalRequest) { + let bids; + + const responseBody = serverResponse.body; + let fledgeAuctionConfigs = null; + + if (responseBody.bidid && isArray(responseBody?.ext?.igbid)) { + // we have fledge response + // mimic the original response ([{},...]) + bids = this.interpretOrtbResponse({ body: responseBody.seatbid[0]?.bid }, originalRequest); + + const seller = responseBody.ext.seller; + const decisionLogicUrl = responseBody.ext.decisionLogicUrl; + const sellerTimeout = 'sellerTimeout' in responseBody.ext ? { sellerTimeout: responseBody.ext.sellerTimeout } : {}; + responseBody.ext.igbid.forEach((igbid) => { + const perBuyerSignals = {}; + igbid.igbuyer.forEach(buyerItem => { + perBuyerSignals[buyerItem.igdomain] = buyerItem.buyersignal + }); + fledgeAuctionConfigs = fledgeAuctionConfigs || {}; + fledgeAuctionConfigs[igbid.impid] = mergeDeep( + { + seller, + decisionLogicUrl, + interestGroupBuyers: Object.keys(perBuyerSignals), + perBuyerSignals, + }, + sellerTimeout + ); + }); + } else { + bids = this.interpretOrtbResponse(serverResponse, originalRequest); + } + + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return Object.assign({ + bidId, + auctionSignals: {} + }, cfg); + }); + logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); + return { + bids, + fledgeAuctionConfigs, + } + } + return bids; } }; registerBidder(spec); @@ -154,7 +207,7 @@ function applyFloor(slot) { * @param {object} slot Ad Unit Params by Prebid * @returns {object} Imp by OpenRTB 2.5 §3.2.4 */ -function mapImpression(slot) { +function mapImpression(slot, bidderRequest) { const imp = { id: slot.bidId, banner: mapBanner(slot), @@ -167,6 +220,14 @@ function mapImpression(slot) { imp.bidfloor = bidfloor; } + if (bidderRequest.fledgeEnabled) { + imp.ext = imp.ext || {}; + imp.ext.ae = slot?.ortb2Imp?.ext?.ae + } else { + if (imp.ext?.ae) { + delete imp.ext.ae; + } + } return imp; } diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index e32d2169c5c..05594c63132 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -108,6 +108,13 @@ * @param {UserConsentData} userConsent */ +/** + * @function? + * @summary on data deletion request + * @name RtdSubmodule#onDataDeletionRequest + * @param {SubmoduleConfig} config + */ + /** * @interface ModuleConfig */ @@ -156,7 +163,7 @@ import {getHook, module} from '../../src/hook.js'; import {logError, logInfo, logWarn} from '../../src/utils.js'; import * as events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; -import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js'; import {find} from '../../src/polyfill.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; @@ -230,6 +237,7 @@ export function init(config) { _dataProviders = realTimeData.dataProviders; setEventsListeners(); getHook('startAuction').before(setBidRequestsData, 20); // RTD should run before FPD + adapterManager.callDataDeletionRequest.before(onDataDeletionRequest); initSubModules(); }); } @@ -238,6 +246,7 @@ function getConsentData() { return { gdpr: gdprDataHandler.getConsentData(), usp: uspDataHandler.getConsentData(), + gpp: gppDataHandler.getConsentData(), coppa: !!(config.getConfig('coppa')) } } @@ -386,5 +395,18 @@ export function deepMerge(arr) { }, {}); } +export function onDataDeletionRequest(next, ...args) { + subModules.forEach((sm) => { + if (typeof sm.onDataDeletionRequest === 'function') { + try { + sm.onDataDeletionRequest(sm.config); + } catch (e) { + logError(`Error executing ${sm.name}.onDataDeletionRequest`, e) + } + } + }); + next.apply(this, args); +} + module('realTimeData', attachRealTimeDataProvider); init(config); diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 223385159a5..76043b71c64 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -62,15 +62,20 @@ const cache = { const BID_REJECTED_IPF = 'rejected-ipf'; -export let rubiConf = { - pvid: generateUUID().slice(0, 8), - analyticsEventDelay: 0, - dmBilling: { - enabled: false, - vendors: [], - waitForAuction: true +export let rubiConf; +export const resetRubiConf = () => { + rubiConf = { + pvid: generateUUID().slice(0, 8), + analyticsEventDelay: 0, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + } } -}; +} +resetRubiConf(); + // we are saving these as global to this module so that if a pub accidentally overwrites the entire // rubicon object, then we do not lose other data config.getConfig('rubicon', config => { @@ -418,7 +423,7 @@ function getBidPrice(bid) { } } -export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { +export function parseBidResponse(bid, previousBidResponse) { // The current bidResponse for this matching requestId/bidRequestId let responsePrice = getBidPrice(bid) // we need to compare it with the previous one (if there was one) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 254799b995d..bd53e9d5104 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -134,7 +134,8 @@ var sizeMap = { 574: '620x891', 576: '610x877', 578: '980x552', - 580: '505x656' + 580: '505x656', + 622: '192x160' }; _each(sizeMap, (item, key) => sizeMap[item] = key); diff --git a/modules/scatteredBidAdapter.js b/modules/scatteredBidAdapter.js new file mode 100644 index 00000000000..47dc09cd1b2 --- /dev/null +++ b/modules/scatteredBidAdapter.js @@ -0,0 +1,72 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, logInfo } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'scattered'; +const GVLID = 1179; +export const converter = ortbConverter({ + context: { + mediaType: BANNER, + ttl: 360, + netRevenue: true + } +}) + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + // 1. + isBidRequestValid: function (bid) { + const bidderDomain = deepAccess(bid, 'params.bidderDomain') + if (bidderDomain === undefined || bidderDomain === '') { + return false + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes') + if (sizes === undefined || sizes.length < 1) { + return false + } + + return true + }, + + // 2. + buildRequests: function (bidRequests, bidderRequest) { + return { + method: 'POST', + url: 'https://' + getKeyOnAny(bidRequests, 'params.bidderDomain'), + data: converter.toORTB({ bidderRequest, bidRequests }), + options: { + contentType: 'application/json' + }, + }; + }, + + // 3. + interpretResponse: function (response, request) { + if (!response.body) return; + return converter.fromORTB({ response: response.body, request: request.data }).bids; + }, + + // 4 + onBidWon: function (bid) { + logInfo('onBidWon', bid) + } +} + +function getKeyOnAny(collection, key) { + for (let i = 0; i < collection.length; i++) { + const result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +registerBidder(spec); diff --git a/modules/scatteredBidAdapter.md b/modules/scatteredBidAdapter.md new file mode 100644 index 00000000000..031d953e32b --- /dev/null +++ b/modules/scatteredBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Scattered Adapter +Module Type: Bidder Adapter +Maintainer: office@scattered.pl +``` + +# Description + +Module that connects to Scattered's demand sources. +It uses OpenRTB standard to communicate between the adapter and bidding servers. + +# Test Parameters + +```javascript + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "scattered", + params: { + bidderDomain: "prebid-test.scattered.eu/bid", + test: 0 + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/schain.js b/modules/schain.js index ef1c6da78ce..eac2cb93b0a 100644 --- a/modules/schain.js +++ b/modules/schain.js @@ -1,6 +1,18 @@ import { config } from '../src/config.js'; import adapterManager from '../src/adapterManager.js'; -import { isNumber, isStr, isArray, isPlainObject, hasOwn, logError, isInteger, _each, logWarn } from '../src/utils.js'; +import { + isNumber, + isStr, + isArray, + isPlainObject, + hasOwn, + logError, + isInteger, + _each, + logWarn, + deepAccess, deepSetValue +} from '../src/utils.js'; +import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md @@ -181,3 +193,14 @@ export function init() { } init() + +export function setOrtbSourceExtSchain(ortbRequest, bidderRequest, context) { + if (!deepAccess(ortbRequest, 'source.ext.schain')) { + const schain = deepAccess(context, 'bidRequests.0.schain'); + if (schain) { + deepSetValue(ortbRequest, 'source.ext.schain', schain); + } + } +} + +registerOrtbProcessor({type: REQUEST, name: 'sourceExtSchain', fn: setOrtbSourceExtSchain}); diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 64b9cd5d4aa..29953da7ffa 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -2,120 +2,111 @@ 'use strict'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE } from '../src/mediaTypes.js'; -import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; +import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import { _map, isArray, isEmpty, deepSetValue, replaceAuctionPrice } from '../src/utils.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const BIDDER_CODE = 'seedingAlliance'; const GVL_ID = 371; +const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; -const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; +const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - - body: { - id: 1, - name: 'data', - type: 2 - }, - - sponsoredBy: { - id: 2, - name: 'data', - type: 1 - }, - - image: { - id: 3, - type: 3, - name: 'img' - }, - - cta: { - id: 4, - type: 12, - name: 'data' - }, - - icon: { - id: 5, - type: 1, - name: 'img' - } + title: { id: 0, name: 'title' }, + body: { id: 1, name: 'data', type: 2 }, + sponsoredBy: { id: 2, name: 'data', type: 1 }, + image: { id: 3, type: 3, name: 'img' }, + cta: { id: 4, type: 12, name: 'data' }, + icon: { id: 5, type: 1, name: 'img' } }; export const spec = { code: BIDDER_CODE, - gvlid: GVL_ID, + supportedMediaTypes: [NATIVE, BANNER], - supportedMediaTypes: [NATIVE], - - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return !!bid.params.adUnitId; }, - buildRequests: (validBidRequests, bidderRequest) => { + buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; - const tid = bidderRequest.auctionId; - const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; let url = bidderRequest.refererInfo.page; - const imp = validBidRequests.map((bid, id) => { - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - - const asset = { - required: bidParams.required & 1 - }; + const imps = validBidRequests.map((bidRequest, id) => { + const imp = { + id: String(id + 1), + tagid: bidRequest.params.adUnitId + }; - if (props) { - asset.id = props.id; + /** + * Native Ad + */ + if (bidRequest.nativeParams) { + const assets = _map(bidRequest.nativeParams, (nativeAsset, key) => { + const props = NATIVE_PARAMS[key]; + + if (props) { + let wmin, hmin, w, h; + let aRatios = nativeAsset.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; + } - let w, h; + if (nativeAsset.sizes) { + const sizes = flatten(nativeAsset.sizes); + w = parseInt(sizes[0], 10); + h = parseInt(sizes[1], 10); + } - if (bidParams.sizes) { - w = bidParams.sizes[0]; - h = bidParams.sizes[1]; + const asset = { + id: props.id, + required: nativeAsset.required & 1 + }; + + asset[props.name] = { + len: nativeAsset.len, + type: props.type, + wmin, + hmin, + w, + h + }; + + return asset; + } else { + // TODO Filter impressions with required assets we don't support } + }).filter(Boolean); - asset[props.name] = { - len: bidParams.len, - type: props.type, - w, - h - }; + imp.native = { + request: { + assets + } + }; + } else { + let sizes = transformSizes(bidRequest.sizes); - return asset; + imp.banner = { + format: sizes, + w: sizes[0] ? sizes[0].w : 0, + h: sizes[0] ? sizes[0].h : 0 } - }) - .filter(Boolean); + } - if (bid.params.url) { - url = bid.params.url; + if (bidRequest.params.url) { + url = bidRequest.params.url; } - return { - id: String(id + 1), - tagid: bid.params.adUnitId, - tid: tid, - pt: pt, - native: { - request: { - assets - } - } - }; + return imp; }); const request = { @@ -123,12 +114,9 @@ export const spec = { site: { page: url }, - device: { - ua: navigator.userAgent - }, - cur, - imp, - user: {}, + cur: [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR], + imp: imps, + tmax: bidderRequest.timeout, regs: { ext: { gdpr: 0, @@ -137,23 +125,22 @@ export const spec = { } }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { + request.user = {}; + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); } return { method: 'POST', - url: ENDPOINT_URL, + url: config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL, data: JSON.stringify(request), - options: { - contentType: 'application/json' - }, - bids: validBidRequests + bidRequests: validBidRequests }; }, - interpretResponse: function(serverResponse, { bids }) { + interpretResponse: function (serverResponse, { bidRequests }) { if (isEmpty(serverResponse.body)) { return []; } @@ -165,35 +152,72 @@ export const spec = { return result; }, []) : []; - return bids - .map((bid, id) => { + return bidRequests + .map((bidRequest, id) => { const bidResponse = bidResponses[id]; + const type = bidRequest.nativeParams ? NATIVE : BANNER; + if (bidResponse) { - return { - requestId: bid.bidId, + const bidObject = { + requestId: bidRequest.bidId, // TODO get this value from response? cpm: bidResponse.price, creativeId: bidResponse.crid, - ttl: 1000, - netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), + ttl: 600, + netRevenue: true, currency: cur, - mediaType: NATIVE, + mediaType: type, bidderCode: BIDDER_CODE, - native: parseNative(bidResponse), meta: { advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] } }; + + if (type === NATIVE) { + bidObject.native = parseNative(bidResponse); + bidObject.mediaType = NATIVE; + } + + if (type === BANNER) { + bidObject.ad = replaceAuctionPrice(bidResponse.adm, bidResponse.price); + bidObject.width = bidResponse.w; + bidObject.height = bidResponse.h; + bidObject.mediaType = BANNER; + } + + return bidObject; } }) .filter(Boolean); } }; -registerBidder(spec); +function transformSizes(requestSizes) { + if (!isArray(requestSizes)) { + return []; + } + + if (requestSizes.length === 2 && !isArray(requestSizes[0])) { + return [{ + w: parseInt(requestSizes[0], 10), + h: parseInt(requestSizes[1], 10) + }]; + } else if (isArray(requestSizes[0])) { + return requestSizes.map(item => ({ + w: parseInt(item[0], 10), + h: parseInt(item[1], 10) + })); + } + + return []; +} + +function flatten(arr) { + return [].concat(...arr); +} function parseNative(bid) { - const {assets, link, imptrackers} = bid.adm.native; + const { assets, link, imptrackers } = bid.adm.native; let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -228,15 +252,4 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} +registerBidder(spec); diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 5c4a3af866c..f54245a41ab 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -1,18 +1,18 @@ import { isArray, _map, triggerPixel } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { VIDEO, BANNER } from '../src/mediaTypes.js' +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'seedtag'; const SEEDTAG_ALIAS = 'st'; const SEEDTAG_SSP_ENDPOINT = 'https://s.seedtag.com/c/hb/bid'; const SEEDTAG_SSP_ONTIMEOUT_ENDPOINT = 'https://s.seedtag.com/se/hb/timeout'; -const ALLOWED_PLACEMENTS = { - inImage: true, - inScreen: true, - inArticle: true, - banner: true, - video: true -} +const ALLOWED_DISPLAY_PLACEMENTS = [ + 'inScreen', + 'inImage', + 'inArticle', + 'inBanner', +]; // Global Vendor List Id // https://iabeurope.eu/vendor-list-tcf-v2-0/ @@ -20,27 +20,33 @@ const GVLID = 157; const mediaTypesMap = { [BANNER]: 'display', - [VIDEO]: 'video' + [VIDEO]: 'video', }; const deviceConnection = { FIXED: 'fixed', MOBILE: 'mobile', - UNKNOWN: 'unknown' + UNKNOWN: 'unknown', }; const getConnectionType = () => { - const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection || {} + const connection = + navigator.connection || + navigator.mozConnection || + navigator.webkitConnection || + {}; switch (connection.type || connection.effectiveType) { case 'wifi': case 'ethernet': - return deviceConnection.FIXED + return deviceConnection.FIXED; case 'cellular': case 'wimax': - return deviceConnection.MOBILE + return deviceConnection.MOBILE; default: - const isMobile = /iPad|iPhone|iPod/.test(navigator.userAgent) || /android/i.test(navigator.userAgent) - return isMobile ? deviceConnection.UNKNOWN : deviceConnection.FIXED + const isMobile = + /iPad|iPhone|iPod/.test(navigator.userAgent) || + /android/i.test(navigator.userAgent); + return isMobile ? deviceConnection.UNKNOWN : deviceConnection.FIXED; } }; @@ -51,24 +57,46 @@ function mapMediaType(seedtagMediaType) { } function hasVideoMediaType(bid) { - return (!!bid.mediaTypes && !!bid.mediaTypes.video) || (!!bid.params && !!bid.params.video) + return !!bid.mediaTypes && !!bid.mediaTypes.video; +} + +function hasBannerMediaType(bid) { + return !!bid.mediaTypes && !!bid.mediaTypes.banner; } -function hasMandatoryParams(params) { +function hasMandatoryDisplayParams(bid) { + const p = bid.params; return ( - !!params.publisherId && - !!params.adUnitId && - !!params.placement && - !!ALLOWED_PLACEMENTS[params.placement] + !!p.publisherId && + !!p.adUnitId && + ALLOWED_DISPLAY_PLACEMENTS.indexOf(p.placement) > -1 ); } function hasMandatoryVideoParams(bid) { - const videoParams = getVideoParams(bid) + const videoParams = getVideoParams(bid); - return hasVideoMediaType(bid) && !!videoParams.playerSize && + let isValid = + !!bid.params.publisherId && + !!bid.params.adUnitId && + hasVideoMediaType(bid) && + !!videoParams.playerSize && isArray(videoParams.playerSize) && videoParams.playerSize.length > 0; + + switch (bid.params.placement) { + // instream accept only video format + case 'inStream': + return isValid && videoParams.context === 'instream'; + // outstream accept banner/native/video format + default: + return ( + isValid && + videoParams.context === 'outstream' && + hasBannerMediaType(bid) && + hasMandatoryDisplayParams(bid) + ); + } } function buildBidRequest(validBidRequest) { @@ -88,15 +116,11 @@ function buildBidRequest(validBidRequest) { adUnitId: params.adUnitId, adUnitCode: validBidRequest.adUnitCode, placement: params.placement, - requestCount: validBidRequest.bidderRequestsCount || 1 // FIXME : in unit test the parameter bidderRequestsCount is undefined + requestCount: validBidRequest.bidderRequestsCount || 1, // FIXME : in unit test the parameter bidderRequestsCount is undefined }; - if (params.adPosition) { - bidRequest.adPosition = params.adPosition; - } - if (hasVideoMediaType(validBidRequest)) { - bidRequest.videoParams = getVideoParams(validBidRequest) + bidRequest.videoParams = getVideoParams(validBidRequest); } return bidRequest; @@ -112,13 +136,7 @@ function getVideoParams(validBidRequest) { videoParams.h = videoParams.playerSize[0][1]; } - const bidderVideoParams = (validBidRequest.params && validBidRequest.params.video) || {} - // override video params from seedtag bidder params - Object.keys(bidderVideoParams).forEach(key => { - videoParams[key] = validBidRequest.params.video[key] - }) - - return videoParams + return videoParams; } function buildBidResponse(seedtagBid) { @@ -135,8 +153,11 @@ function buildBidResponse(seedtagBid) { ttl: seedtagBid.ttl, nurl: seedtagBid.nurl, meta: { - advertiserDomains: seedtagBid && seedtagBid.adomain && seedtagBid.adomain.length > 0 ? seedtagBid.adomain : [] - } + advertiserDomains: + seedtagBid && seedtagBid.adomain && seedtagBid.adomain.length > 0 + ? seedtagBid.adomain + : [], + }, }; if (mediaType === VIDEO) { @@ -177,19 +198,24 @@ function ttfb() { return ttfb >= 0 && ttfb <= performance.now() ? ttfb : 0; } -export function getTimeoutUrl (data) { +export function getTimeoutUrl(data) { let queryParams = ''; if ( - isArray(data) && data[0] && - isArray(data[0].params) && data[0].params[0] + isArray(data) && + data[0] && + isArray(data[0].params) && + data[0].params[0] ) { const params = data[0].params[0]; - const timeout = data[0].timeout + const timeout = data[0].timeout; queryParams = - '?publisherToken=' + params.publisherId + - '&adUnitId=' + params.adUnitId + - '&timeout=' + timeout; + '?publisherToken=' + + params.publisherId + + '&adUnitId=' + + params.adUnitId + + '&timeout=' + + timeout; } return SEEDTAG_SSP_ONTIMEOUT_ENDPOINT + queryParams; } @@ -207,8 +233,8 @@ export const spec = { */ isBidRequestValid(bid) { return hasVideoMediaType(bid) - ? hasMandatoryParams(bid.params) && hasMandatoryVideoParams(bid) - : hasMandatoryParams(bid.params); + ? hasMandatoryVideoParams(bid) + : hasMandatoryDisplayParams(bid); }, /** @@ -235,21 +261,37 @@ export const spec = { if (gdprApplies !== undefined) payload['ga'] = gdprApplies; payload['cd'] = bidderRequest.gdprConsent.consentString; } - if (bidderRequest.uspConsent) { - payload['uspConsent'] = bidderRequest.uspConsent + payload['uspConsent'] = bidderRequest.uspConsent; } if (validBidRequests[0].schain) { payload.schain = validBidRequests[0].schain; } - const payloadString = JSON.stringify(payload) + let coppa = config.getConfig('coppa'); + if (coppa) { + payload.coppa = coppa; + } + + if (bidderRequest.gppConsent) { + payload.gppConsent = { + gppString: bidderRequest.gppConsent.gppString, + applicableSections: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest.ortb2?.regs?.gpp) { + payload.gppConsent = { + gppString: bidderRequest.ortb2.regs.gpp, + applicableSections: bidderRequest.ortb2.regs.gpp_sid + } + } + + const payloadString = JSON.stringify(payload); return { method: 'POST', url: SEEDTAG_SSP_ENDPOINT, - data: payloadString - } + data: payloadString, + }; }, /** @@ -258,10 +300,10 @@ export const spec = { * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse) { + interpretResponse: function (serverResponse) { const serverBody = serverResponse.body; if (serverBody && serverBody.bids && isArray(serverBody.bids)) { - return _map(serverBody.bids, function(bid) { + return _map(serverBody.bids, function (bid) { return buildBidResponse(bid); }); } else { @@ -303,6 +345,6 @@ export const spec = { if (bid && bid.nurl) { triggerPixel(bid.nurl); } - } -} + }, +}; registerBidder(spec); diff --git a/modules/seedtagBidAdapter.md b/modules/seedtagBidAdapter.md index 061a254c5fa..8ccfcfb701e 100644 --- a/modules/seedtagBidAdapter.md +++ b/modules/seedtagBidAdapter.md @@ -59,3 +59,63 @@ const adUnits = [ } ] ``` + +## InBanner +```js +const adUnits = [ + { + code: '/21804003197/prebid_test_300x250', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'seedtag', + params: { + publisherId: '0000-0000-01', // required + adUnitId: '0000', // required + placement: 'inBanner', // required + } + } + ] + } +] +``` + +## inStream Video +```js +var adUnits = [{ + code: 'video', + mediaTypes: { + video: { + context: 'instream', // required + playerSize: [640, 360], // required + // Video object as specified in OpenRTB 2.5 + mimes: ['video/mp4'], // recommended + minduration: 5, // optional + maxduration: 60, // optional + boxingallowed: 1, // optional + skip: 1, // optional + startdelay: 1, // optional + linearity: 1, // optional + battr: [1, 2], // optional + maxbitrate: 10, // optional + playbackmethod: [1], // optional + delivery: [1], // optional + placement: 1, // optional + } + }, + bids: [ + { + bidder: 'seedtag', + params: { + publisherId: '0000-0000-01', // required + adUnitId: '0000', // required + placement: 'inStream', // required + } + } + ] +}]; +``` diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 99a34ae17f4..7562b472047 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -9,8 +9,9 @@ import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUI import {submodule} from '../src/hook.js'; import { coppaDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; +import {VENDORLESS_GVLID} from '../src/consentHandler.js'; -export const storage = getStorageManager({moduleName: 'pubCommonId'}); +export const storage = getStorageManager({moduleName: 'pubCommonId', gvlid: VENDORLESS_GVLID}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; @@ -73,6 +74,7 @@ export const sharedIdSystemSubmodule = { */ name: 'sharedId', aliasName: 'pubCommonId', + gvlid: VENDORLESS_GVLID, /** * decode the stored id value for passing to bid requests diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 369276e7638..40ee3d8b973 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -1,12 +1,13 @@ /** * This module adds Sirdata provider to the real time data module + * and now supports Seller Defined Audience * The {@link module:modules/realTimeData} module is required * The module will fetch segments (user-centric) and categories (page-centric) from Sirdata server * The module will automatically handle user's privacy and choice in California (IAB TL CCPA Framework) and in Europe (IAB EU TCF FOR GDPR) * @module modules/sirdataRtdProvider * @requires module:modules/realTimeData */ -import {deepAccess, deepEqual, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; +import {deepAccess, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; import {findIndex} from '../src/polyfill.js'; @@ -16,6 +17,52 @@ import {config} from '../src/config.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'SirdataRTDModule'; +const ORTB2_NAME = 'sirdata.com'; + +const partnerIds = { + 'criteo': 27443, + 'openx': 30342, + 'pubmatic': 30345, + 'smaato': 27520, + 'triplelift': 27518, + 'yahoossp': 30339, + 'rubicon': 27452, + 'appnexus': 27446, + 'appnexusAst': 27446, + 'brealtime': 27446, + 'emxdigital': 27446, + 'pagescience': 27446, + 'gourmetads': 33394, + 'matomy': 27446, + 'featureforward': 27446, + 'oftmedia': 27446, + 'districtm': 27446, + 'adasta': 27446, + 'beintoo': 27446, + 'gravity': 27446, + 'msq_classic': 27878, + 'msq_max': 27878, + '366_apx': 27878, + 'mediasquare': 27878, + 'smartadserver': 27440, + 'smart': 27440, + 'proxistore': 27484, + 'ix': 27248, + 'sdRtdForGpt': 27449, + 'smilewanted': 28690, + 'taboola': 33379, + 'ttd': 33382, + 'zeta_global': 33385, + 'teads': 33388, + 'conversant': 33391, + 'improvedigital': 33397, + 'invibes': 33400, + 'sublime': 33403, + 'rtbhouse': 33406, + 'zeta_global_ssp': 33385, +}; + +let CONTEXT_ONLY = true; export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { moduleConfig.params = moduleConfig.params || {}; @@ -46,9 +93,10 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, if (!sirdataDomain || !gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { sirdataDomain = 'sddan.com'; sendWithCredentials = true; + CONTEXT_ONLY = false; } - // TODO: is 'page' the right value here? - var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().page; + + var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().stack.pop() || getRefererInfo().page; const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + encodeURIComponent(actualUrl) : ''); @@ -86,39 +134,74 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, }); } -export function setGlobalOrtb2(ortb2, segments, categories) { +export function setGlobalOrtb2Sda(ortb2Fragments, data, segtaxid, cattaxid) { try { - let addOrtb2 = {}; - let testGlobal = ortb2 || {} - if (!deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { - deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + if (!isEmpty(data.segments)) { + applyGlobalOrtb2Sda(ortb2Fragments, 'user', data.segments, segtaxid); } - if (!deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) { - deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); - } - if (!isEmpty(addOrtb2)) { - mergeDeep(ortb2, addOrtb2); + if (!isEmpty(data.categories)) { + applyGlobalOrtb2Sda(ortb2Fragments, 'site', data.categories, cattaxid); } } catch (e) { logError(e) } + return true; +} +export function applyGlobalOrtb2Sda(ortb2Fragments, type, segments, segtaxValue) { + try { + let ortb2Data = [{ + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })), + }]; + if (segtaxValue) { + ortb2Data[0].ext = { segtax: segtaxValue }; + } + let ortb2Conf = (type == 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); + mergeDeep(ortb2Fragments, ortb2Conf); + } catch (e) { + logError(e) + } return true; } -export function setBidderOrtb2(bidderOrtb2, bidder, segments, categories) { +export function setBidderOrtb2Sda(ortb2Fragments, bidder, data, segtaxid, cattaxid) { try { - let addOrtb2 = {}; - let testBidder = bidderOrtb2[bidder]; - if (!deepAccess(testBidder, 'user.ext.data.sd_rtd') || !deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { - deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); + if (!isEmpty(data.segments)) { + applyBidderOrtb2Sda(ortb2Fragments, bidder, 'user', data.segments, segtaxid); } - if (!deepAccess(testBidder, 'site.ext.data.sd_rtd') || !deepEqual(testBidder.site.ext.data.sd_rtd, categories)) { - deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); + if (!isEmpty(data.categories)) { + applyBidderOrtb2Sda(ortb2Fragments, bidder, 'site', data.categories, cattaxid); } - if (!isEmpty(addOrtb2)) { - mergeDeep(bidderOrtb2[bidder], addOrtb2) + } catch (e) { + logError(e) + } + return true; +} + +export function applyBidderOrtb2Sda(ortb2Fragments, bidder, type, segments, segtaxValue) { + try { + let ortb2Data = [{ + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })), + }]; + if (segtaxValue) { + ortb2Data[0].ext = { segtax: segtaxValue }; } + let ortb2Conf = (type == 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); + mergeDeep(ortb2Fragments, {[bidder]: ortb2Conf}); + } catch (e) { + logError(e) + } + return true; +} + +export function setBidderOrtb2(bidderOrtb2Fragments, bidder, path, segments) { + try { + if (isEmpty(segments)) { return; } + let ortb2Conf = {}; + deepSetValue(ortb2Conf, path, segments || {}); + mergeDeep(bidderOrtb2Fragments, {[bidder]: ortb2Conf}); } catch (e) { logError(e) } @@ -137,16 +220,16 @@ export function loadCustomFunction(todo, adUnit, list, data, bid) { return true; } -export function getSegAndCatsArray(data, minScore) { - var sirdataData = {'segments': [], 'categories': []}; +export function getSegAndCatsArray(data, minScore, pid) { + let sirdataData = {'segments': [], 'categories': []}; minScore = minScore && typeof minScore == 'number' ? minScore : 30; try { if (data && data.contextual_categories) { for (let catId in data.contextual_categories) { - if (data.contextual_categories.hasOwnProperty(catId)) { + if (data.contextual_categories.hasOwnProperty(catId) && data.contextual_categories[catId]) { let value = data.contextual_categories[catId]; if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { - sirdataData.categories.push(catId.toString()); + sirdataData.categories.push((pid ? pid.toString() + 'cc' : '') + catId.toString()); } } } @@ -157,8 +240,11 @@ export function getSegAndCatsArray(data, minScore) { try { if (data && data.segments) { for (let segId in data.segments) { - if (data.segments.hasOwnProperty(segId)) { - sirdataData.segments.push(data.segments[segId].toString()); + if (data.segments.hasOwnProperty(segId) && data.segments[segId]) { + sirdataData.segments.push((pid ? pid.toString() + 'us' : '') + data.segments[segId].toString()); + if (pid && CONTEXT_ONLY) { + sirdataData.categories.push(pid.toString() + 'uc' + data.segments[segId].toString()); + } } } } @@ -168,34 +254,77 @@ export function getSegAndCatsArray(data, minScore) { return sirdataData; } +export function applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { + // only share SDA data if whitelisted + if (!biddersParamsExist || indexFound) { + // SDA Publisher + let sirdataDataForSDA = getSegAndCatsArray(data, minScore, moduleConfig.params.partnerId); + setBidderOrtb2Sda(reqBids.ortb2Fragments?.bidder, bid.bidder, sirdataDataForSDA, data.segtaxid, data.cattaxid); + } + + // always share SDA for curation + let curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); + if (curationId) { + // seller defined audience & bidder specific data + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + // Get Bidder Specific Data + let curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, null); + sirdataList = sirdataList.concat(curationData.segments).concat(curationData.categories); + + // SDA Partners + let curationDataForSDA = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, curationId); + setBidderOrtb2Sda(reqBids.ortb2Fragments?.bidder, bid.bidder, curationDataForSDA, data.shared_taxonomy[curationId].segtaxid, data.shared_taxonomy[curationId].cattaxid); + } + } + + // Apply custom function or return Bidder Specific Data if publisher is ok + if (sirdataList && sirdataList.length > 0 && (!biddersParamsExist || indexFound)) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + return loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList, data, bid); + } else { + return sirdataList; + } + } +} + +export function applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { + let specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.ext.data', {sd_rtd: specificData}); + } +} + export function addSegmentData(reqBids, data, moduleConfig, onDone) { const adUnits = reqBids.adUnits; moduleConfig = moduleConfig || {}; moduleConfig.params = moduleConfig.params || {}; const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; - var sirdataData = getSegAndCatsArray(data, globalMinScore); + var sirdataData = getSegAndCatsArray(data, globalMinScore, null); const sirdataList = sirdataData.segments.concat(sirdataData.categories); - var sirdataMergedList = []; - var curationData = {'segments': [], 'categories': []}; - var curationId = '1'; const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders)); - // Global ortb2 - if (!biddersParamsExist) { - setGlobalOrtb2(reqBids.ortb2Fragments?.global, sirdataData.segments, sirdataData.categories); + // Global ortb2 SDA + if (data.global_taxonomy && !isEmpty(data.global_taxonomy)) { + let globalData = {'segments': [], 'categories': []}; + for (let i in data.global_taxonomy) { + if (!isEmpty(data.global_taxonomy[i])) { + globalData = getSegAndCatsArray(data.global_taxonomy[i], globalMinScore, null); + setGlobalOrtb2Sda(reqBids.ortb2Fragments?.global, globalData, data.global_taxonomy[i].segtaxid, data.global_taxonomy[i].cattaxid); + } + } } // Google targeting if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { try { - // For curation Google is pid 27449 - curationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : '27449'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore); + let gptCurationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : (partnerIds['sdRtdForGpt'] ? partnerIds['sdRtdForGpt'] : null)); + let sirdataMergedList = sirdataList; + if (gptCurationId && data.shared_taxonomy && data.shared_taxonomy[gptCurationId]) { + let gamCurationData = getSegAndCatsArray(data.shared_taxonomy[gptCurationId], globalMinScore, null); + sirdataMergedList = sirdataMergedList.concat(gamCurationData.segments).concat(gamCurationData.categories); } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); window.googletag.pubads().getSlots().forEach(function (n) { if (typeof n.setTargeting !== 'undefined' && sirdataMergedList && sirdataMergedList.length > 0) { n.setTargeting('sd_rtd', sirdataMergedList); @@ -221,259 +350,108 @@ export function addSegmentData(reqBids, data, moduleConfig, onDone) { }) : false); indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); try { - curationData = {'segments': [], 'categories': []}; - sirdataMergedList = []; - let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore); + let specificData = null; + + switch (bid.bidder) { + case 'appnexus': + case 'appnexusAst': + case 'brealtime': + case 'emxdigital': + case 'pagescience': + case 'gourmetads': + case 'matomy': + case 'featureforward': + case 'oftmedia': + case 'districtm': + case 'adasta': + case 'beintoo': + case 'gravity': + case 'msq_classic': + case 'msq_max': + case '366_apx': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + deepSetValue(bid, 'params.keywords.sd_rtd', specificData); + } + break; - if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { - switch (bid.bidder) { - case 'appnexus': - case 'appnexusAst': - case 'brealtime': - case 'emxdigital': - case 'pagescience': - case 'gourmetads': - case 'matomy': - case 'featureforward': - case 'oftmedia': - case 'districtm': - case 'adasta': - case 'beintoo': - case 'gravity': - case 'msq_classic': - case 'msq_max': - case '366_apx': - // For curation Xandr is pid 27446 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27446'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - deepSetValue(bid, 'params.keywords.sd_rtd', sirdataMergedList); - } - } - break; - - case 'smartadserver': - case 'smart': + case 'smartadserver': + case 'smart': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { var target = []; if (bid.hasOwnProperty('params') && bid.params.hasOwnProperty('target')) { target.push(bid.params.target); } - // For curation Smart is pid 27440 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27440'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - sirdataMergedList.forEach(function (entry) { - if (target.indexOf('sd_rtd=' + entry) === -1) { - target.push('sd_rtd=' + entry); - } - }); - deepSetValue(bid, 'params.target', target.join(';')); - } - } - break; - - case 'rubicon': - // For curation Magnite is pid 27518 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27452'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'ix': - var ixConfig = config.getConfig('ix.firstPartyData.sd_rtd'); - if (!ixConfig) { - // For curation index is pid 27248 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - var cappIxCategories = []; - var ixLength = 0; - var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); - // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters - sirdataMergedList.forEach(function (entry) { - if (ixLength < ixLimit) { - cappIxCategories.push(entry); - ixLength += entry.toString().length; - } - }); - config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); - } - } - } - break; - - case 'proxistore': - // For curation Proxistore is pid 27484 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27484'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } else { - data.shared_taxonomy[curationId] = {contextual_categories: {}}; - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - deepSetValue(bid, 'ortb2.user.ext.data', { - segments: sirdataData.segments.concat(curationData.segments), - contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories} - }); - } - } - break; - - case 'criteo': - // For curation Smart is pid 27443 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27443'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'triplelift': - // For curation Triplelift is pid 27518 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27518'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'avct': - case 'avocet': - // For curation Avocet is pid 27522 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27522'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'smaato': - // For curation Smaato is pid 27520 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27520'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'yahoossp': - // For curation Yahoo is pid 30339 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30339'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - case 'openx': - // For curation OpenX is pid 30342 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30342'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + specificData.forEach(function (entry) { + if (target.indexOf('sd_rtd=' + entry) === -1) { + target.push('sd_rtd=' + entry); } - } - break; - - case 'pubmatic': - // For curation Pubmatic is pid 30345 - curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30345'); - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); - } - sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); - if (sirdataMergedList && sirdataMergedList.length > 0) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); - } else { - setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); - } - } - break; - - default: - if (!biddersParamsExist || indexFound) { - if (!deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) { - deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories); - } - if (!deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) { - deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments); + }); + deepSetValue(bid, 'params.target', target.join(';')); + } + break; + + case 'ix': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + let ixConfig = config.getConfig('ix.firstPartyData.sd_rtd'); + if (!ixConfig && specificData && specificData.length > 0) { + let cappIxCategories = []; + let ixLength = 0; + let ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); + // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters + specificData.forEach(function (entry) { + if (ixLength < ixLimit) { + cappIxCategories.push(entry); + ixLength += entry.toString().length; } + }); + config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + } + break; + + case 'proxistore': + specificData = applySdaGetSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + if (specificData && specificData.length > 0) { + let psCurationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); + if (!data.shared_taxonomy || !data.shared_taxonomy[psCurationId]) { + data.shared_taxonomy[psCurationId] = {segments: [], contextual_categories: {}, segtaxid: null, cattaxid: null}; } - } + let psCurationData = getSegAndCatsArray(data.shared_taxonomy[psCurationId], minScore, null); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.ext.data', { + segments: sirdataData.segments.concat(psCurationData.segments), + contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[psCurationId].contextual_categories} + }); + } + break; + + case 'rubicon': + case 'criteo': + case 'triplelift': + case 'smaato': + case 'yahoossp': + case 'openx': + case 'pubmatic': + case 'smilewanted': + case 'taboola': + case 'ttd': + case 'zeta_global': + case 'zeta_global_ssp': + case 'teads': + case 'conversant': + case 'improvedigital': + case 'invibes': + case 'sublime': + case 'rtbhouse': + case 'mediasquare': + applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + break; + + default: + if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { + applySdaAndDefaultSpecificData(data, sirdataList, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); + } } } catch (e) { logError(e); diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index c783f35d915..18ce56c7a80 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -1,11 +1,14 @@ -import { deepAccess, getDNT, deepSetValue, logInfo, logError, isEmpty, getAdUnitSizes, fill, chunk, getMaxValueFromArray, getMinValueFromArray } from '../src/utils.js'; +import { deepAccess, isNumber, getDNT, deepSetValue, logInfo, logError, isEmpty, getAdUnitSizes, fill, chunk, getMaxValueFromArray, getMinValueFromArray } from '../src/utils.js'; +import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; -import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; +import {ADPOD, BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +import CONSTANTS from '../src/constants.json'; +const { NATIVE_IMAGE_TYPES } = CONSTANTS; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.6' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.7' const CURRENCY = 'USD'; const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { @@ -91,6 +94,12 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { } } + const nativeOrtbRequest = bidRequest.nativeOrtbRequest; + if (nativeOrtbRequest) { + const nativeRequest = Object.assign({}, requestTemplate, createNativeImp(bidRequest, nativeOrtbRequest)); + requests.push(nativeRequest); + } + return requests; } @@ -109,11 +118,11 @@ const buildServerRequest = (validBidRequest, data) => { export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], gvlid: 82, /** - * Determines whether or not the given bid request is valid. + * Determines whether the given bid request is valid. * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. @@ -171,6 +180,7 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (serverResponse, bidRequest) => { @@ -239,6 +249,11 @@ export const spec = { resultingBid.mediaType = VIDEO; bids.push(resultingBid); break; + case 'Native': + resultingBid.native = createNativeAd(bid.adm); + resultingBid.mediaType = NATIVE; + bids.push(resultingBid); + break; default: logInfo('[SMAATO] Invalid ad type:', smtAdType); } @@ -297,6 +312,13 @@ const createRichmediaAd = (adm) => { return markup + ''; }; +const createNativeAd = (adm) => { + const nativeResponse = JSON.parse(adm).native; + return { + ortb: nativeResponse + } +}; + function createBannerImp(bidRequest) { const adUnitSizes = getAdUnitSizes(bidRequest); const sizes = adUnitSizes.map((size) => ({w: size[0], h: size[1]})); @@ -342,6 +364,33 @@ function createVideoImp(bidRequest, videoMediaType) { }; } +function createNativeImp(bidRequest, nativeRequest) { + return { + imp: [{ + id: bidRequest.bidId, + tagid: deepAccess(bidRequest, 'params.adspaceId'), + bidfloor: getBidFloor(bidRequest, NATIVE, getNativeMainImageSize(nativeRequest)), + native: { + request: JSON.stringify(nativeRequest), + ver: '1.2' + } + }] + }; +} + +function getNativeMainImageSize(nativeRequest) { + const mainImage = find(nativeRequest.assets, asset => asset.hasOwnProperty('img') && asset.img.type === NATIVE_IMAGE_TYPES.MAIN) + if (mainImage) { + if (isNumber(mainImage.img.w) && isNumber(mainImage.img.h)) { + return [[mainImage.img.w, mainImage.img.h]] + } + if (isNumber(mainImage.img.wmin) && isNumber(mainImage.img.hmin)) { + return [[mainImage.img.wmin, mainImage.img.hmin]] + } + } + return [] +} + function createAdPodImp(bidRequest, videoMediaType) { const tagid = deepAccess(bidRequest, 'params.adbreakId') const bce = config.getConfig('adpod.brandCategoryExclusion') diff --git a/modules/smaatoBidAdapter.md b/modules/smaatoBidAdapter.md index 41e1c952f2a..170880c3fc0 100644 --- a/modules/smaatoBidAdapter.md +++ b/modules/smaatoBidAdapter.md @@ -63,6 +63,84 @@ var adUnits = [{ }]; ``` +For native adunits: + +``` +var adUnits = [{ + "code": "native unit", + "mediaTypes": { + native: { + ortb: { + ver: "1.2", + assets: [ + { + id: 1, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }, + { + id: 2, + required: 1, + img: { + type: 2, + w: 50, + h: 50 + } + }, + { + id: 3, + required: 1, + title: { + len: 80 + } + }, + { + id: 4, + required: 1, + data: { + type: 1 + } + }, + { + id: 5, + required: 1, + data: { + type: 2 + } + }, + { + id: 6, + required: 0, + data: { + type: 3 + } + }, + { + id: 7, + required: 0, + data: { + type: 12 + } + } + ] + }, + sendTargetingKeys: false, + } + }, + "bids": [{ + "bidder": "smaato", + "params": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + }] +}]; +``` + For adpod adunits: ``` diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 5d64cad27b5..c07b2abe933 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -13,6 +13,7 @@ export const spec = { gvlid: GVL_ID, aliases: ['smart'], // short code supportedMediaTypes: [BANNER, VIDEO], + /** * Determines whether or not the given bid request is valid. * @@ -131,8 +132,9 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - const adServerCurrency = config.getConfig('currency.adServerCurrency'); + const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); + const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); // pull requested transaction ID from bidderRequest.bids[].transactionId return validBidRequests.reduce((bidRequests, bid) => { @@ -142,7 +144,6 @@ export const spec = { pageid: bid.params.pageId, formatid: bid.params.formatId, currencyCode: adServerCurrency, - bidfloor: bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency), targeting: bid.params.target && bid.params.target !== '' ? bid.params.target : undefined, buid: bid.params.buId && bid.params.buId !== '' ? bid.params.buId : undefined, appname: bid.params.appName && bid.params.appName !== '' ? bid.params.appName : undefined, @@ -154,13 +155,26 @@ export const spec = { timeout: config.getConfig('bidderTimeout'), bidId: bid.bidId, prebidVersion: '$prebid.version$', - schain: spec.serializeSupplyChain(bid.schain) + schain: spec.serializeSupplyChain(bid.schain), + sda: sellerDefinedAudience, + sdc: sellerDefinedContext }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; - payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side + } + + if (bidderRequest.gppConsent) { + payload.gpp = bidderRequest.gppConsent.gppString; + payload.gpp_sid = bidderRequest.gppConsent.applicableSections; + } + + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } } if (bid && bid.userId) { @@ -171,24 +185,28 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); - const isAdUnitContainingVideo = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); - if (!isAdUnitContainingVideo && bannerMediaType) { - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && !bannerMediaType) { - spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); - } else if (isAdUnitContainingVideo && bannerMediaType) { - // If there are video and banner media types in the ad unit, we clone the payload - // to create a specific one for video. - let videoPayload = deepClone(payload); + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const isSupportedVideoContext = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); + + if (bannerMediaType || isSupportedVideoContext) { + let type; + if (bannerMediaType) { + type = BANNER; + payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); - spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); - bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + if (isSupportedVideoContext) { + let videoPayload = deepClone(payload); + spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); + videoPayload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, VIDEO); + bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); + } + } else { + type = VIDEO; + spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); + } - payload.sizes = spec.adaptBannerSizes(bannerMediaType.sizes); + payload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, type); bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); } else { bidRequests.push({}); @@ -209,7 +227,7 @@ export const spec = { const bidResponses = []; let response = serverResponse.body; try { - if (response && !response.isNoAd) { + if (response && !response.isNoAd && (response.ad || response.adUrl)) { const bidRequest = JSON.parse(bidRequestString.data); let bidResponse = { @@ -249,24 +267,21 @@ export const spec = { * * @param {object} bid Bid request object * @param {string} currency Ad server currency + * @param {string} mediaType Bid media type * @return {number} Floor price */ - getBidFloor: function (bid, currency) { + getBidFloor: function (bid, currency, mediaType) { if (!isFn(bid.getFloor)) { return DEFAULT_FLOOR; } const floor = bid.getFloor({ currency: currency || 'USD', - mediaType: '*', + mediaType, size: '*' }); - if (isPlainObject(floor) && !isNaN(floor.floor)) { - return floor.floor; - } - - return DEFAULT_FLOOR; + return isPlainObject(floor) && !isNaN(floor.floor) ? floor.floor : DEFAULT_FLOOR; }, /** diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index e5f71265e34..d91b62729bc 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -10,8 +10,10 @@ import { } from '../src/mediaTypes.js'; const BIDDER_CODE = 'smartx'; const URL = 'https://bid.sxp.smartclip.net/bid/1000'; +const GVLID = 115; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [VIDEO], /** * Determines whether or not the given bid request is valid. @@ -67,7 +69,6 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { - // TODO: does the fallback make sense here? const page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; const isPageSecure = !!page.match(/^https:/) @@ -77,6 +78,7 @@ export const spec = { const bidfloor = getBidFloor(bid) || 0; const bidfloorcur = getBidIdParameter('bidfloorcur', bid.params) || 'EUR'; const siteId = getBidIdParameter('siteId', bid.params); + const sitekey = getBidIdParameter('sitekey', bid.params); const domain = getBidIdParameter('domain', bid.params); const cat = getBidIdParameter('cat', bid.params) || ['']; let pubcid = null; @@ -192,11 +194,25 @@ export const spec = { } } + // Add sitekey if available + if (sitekey) { + requestPayload.site.content.ext.sitekey = sitekey; + } + // Add common id if available if (pubcid) { userExt.fpc = pubcid; } + // Add schain object if available + if (bid && bid.schain) { + requestPayload['source'] = { + ext: { + schain: bid.schain + } + }; + } + // Only add the user object if it's not empty if (!isEmpty(userExt)) { requestPayload.user = { @@ -204,9 +220,7 @@ export const spec = { }; } - // requestPayload.user.ext.ver = pbjs.version; - - // Targeting + // Add targeting if (getBidIdParameter('data', bid.params.user)) { var targetingarr = []; for (var i = 0; i < bid.params.user.data.length; i++) { @@ -225,8 +239,6 @@ export const spec = { } } - // Todo: USER ID MODULE - requestPayload.user = { ext: userExt, data: targetingarr @@ -241,7 +253,7 @@ export const spec = { options: { contentType: 'application/json', customHeaders: { - 'x-openrtb-version': '2.3' + 'x-openrtb-version': '2.5' } } }; @@ -269,7 +281,6 @@ export const spec = { } /** * Make sure currency and price are the right ones - * TODO: what about the pre_market_bid partners sizes? */ _each(currentBidRequest.params.pre_market_bids, function (pmb) { if (pmb.deal_id == smartxBid.id) { diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index f078d905e62..e0d6023a794 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -10,6 +10,15 @@ Maintainer: supply@smartyads.com Module that connects to SmartyAds' demand sources +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `sourceid` | required (for prebid.js) | placement ID | "0" | +| `host` | required (for prebid-server) | const value, set to "prebid" | "prebid" | +| `accountid` | required (for prebid-server) | partner ID | "1901" | +| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | + # Test Parameters ``` var adUnits = [ diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js new file mode 100644 index 00000000000..9f275a761c7 --- /dev/null +++ b/modules/smartytechBidAdapter.js @@ -0,0 +1,135 @@ +import {buildUrl, deepAccess} from '../src/utils.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'smartytech'; +export const ENDPOINT_PROTOCOL = 'https'; +export const ENDPOINT_DOMAIN = 'server.smartytech.io'; +export const ENDPOINT_PATH = '/hb/v2/bidder'; + +export const spec = { + supportedMediaTypes: [ BANNER, VIDEO ], + code: BIDDER_CODE, + + isBidRequestValid: function (bidRequest) { + return ( + !!parseInt(bidRequest.params.endpointId) && + spec._validateBanner(bidRequest) && + spec._validateVideo(bidRequest) + ); + }, + + _validateBanner: function(bidRequest) { + const bannerAdUnit = deepAccess(bidRequest, 'mediaTypes.banner'); + + if (bannerAdUnit === undefined) { + return true; + } + + if (!Array.isArray(bannerAdUnit.sizes)) { + return false; + } + + return true; + }, + + _validateVideo: function(bidRequest) { + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); + + if (videoAdUnit === undefined) { + return true; + } + + if (!Array.isArray(videoAdUnit.playerSize)) { + return false; + } + + if (!videoAdUnit.context) { + return false; + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const referer = bidderRequest?.refererInfo?.page || window.location.href; + + const bidRequests = validBidRequests.map((validBidRequest) => { + let video = deepAccess(validBidRequest, 'mediaTypes.video', false); + let banner = deepAccess(validBidRequest, 'mediaTypes.banner', false); + + let oneRequest = { + endpointId: validBidRequest.params.endpointId, + adUnitCode: validBidRequest.adUnitCode, + referer: referer, + bidId: validBidRequest.bidId + }; + + if (video) { + oneRequest.video = video; + } else if (banner) { + oneRequest.banner = banner; + } + + return oneRequest + }); + + let adPartnerRequestUrl = buildUrl({ + protocol: ENDPOINT_PROTOCOL, + hostname: ENDPOINT_DOMAIN, + pathname: ENDPOINT_PATH, + }); + + return { + method: 'POST', + url: adPartnerRequestUrl, + data: bidRequests + }; + }, + + interpretResponse: function (serverResponse, bidRequest) { + if (typeof serverResponse.body === 'undefined') { + return []; + } + + const validBids = bidRequest.data; + const keys = Object.keys(serverResponse.body) + const responseBody = serverResponse.body; + + return keys.filter(key => { + return responseBody[key].ad + }).map(key => { + return { + bid: validBids.find(b => b.adUnitCode === key), + response: responseBody[key] + } + }).map(item => spec._adResponse(item.bid, item.response)); + }, + + _adResponse: function (request, response) { + const bidObject = { + requestId: request.bidId, + adUnitCode: request.adUnitCode, + bidderCode: BIDDER_CODE, + ad: response.ad, + cpm: response.cpm, + width: response.width, + height: response.height, + ttl: 60, + creativeId: response.creativeId, + netRevenue: true, + currency: response.currency, + mediaType: BANNER + } + + if (response.mediaType === VIDEO) { + bidObject.vastXml = response.ad; + bidObject.mediaType = VIDEO; + } + + return bidObject; + }, + +} + +registerBidder(spec); diff --git a/modules/smartytechBidAdapter.md b/modules/smartytechBidAdapter.md new file mode 100644 index 00000000000..9df57ddbde7 --- /dev/null +++ b/modules/smartytechBidAdapter.md @@ -0,0 +1,55 @@ +# Overview + +``` +Module Name: SmartyTech Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@adpartner.pro +``` + +# Description + +Connects to SmartyTech's exchange for bids. + +SmartyTech bid adapter supports Banner and Video + +# Sample Ad Unit: For Publishers +## Sample Banner Ad Unit +``` +var adUnits = [{ + code: '/123123123/prebidjs-banner', + mediaTypes: { + banner: { + sizes: [ + [300, 301], + [300, 250] + ] + } + }, + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}]; +``` + +## Sample Video Ad Unit +``` +var videoAdUnit = { + code: '/123123123/video-vast-banner', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + } + }, + bids: [{ + bidder: 'smartytech', + params: { + endpointId: 12 + } + }] +}; +``` diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index f82e7c9258f..fe14c57d641 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -63,6 +63,11 @@ export const spec = { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side } + + if (bid && bid.userIdAsEids) { + payload.eids = bid.userIdAsEids; + } + var payloadString = JSON.stringify(payload); return { method: 'POST', diff --git a/modules/smnBidAdapter.md b/modules/smnBidAdapter.md new file mode 100644 index 00000000000..c9cb777e743 --- /dev/null +++ b/modules/smnBidAdapter.md @@ -0,0 +1,52 @@ +# Overview + +Module Name: SMN Bidder Adapter +Module Type: Bidder Adapter +Maintainer: smnoffice.belgrade@gmail.com + +# Description + +Connects to SMN demand source to fetch bids. +Banner and Video formats are supported. +Please use ```smn``` as the bidder code. + +# Test Parameters +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "smn", + params: { + zone: '2eb6bd58-865c-47ce-af7f-a918108c3fd2' + } + } + ] + },{ + code: 'mobile-banner-ad-div', + sizes: [[300, 50]], // a mobile size + bids: [ + { + bidder: "smn", + params: { + zone: '62211486-c50b-4356-9f0f-411778d31fcc' + } + } + ] + },{ + code: 'video-ad', + sizes: [[300, 50]], + mediaType: 'video', + bids: [ + { + bidder: "smn", + params: { + zone: 'ebeb1e79-8cb4-4473-b2d0-2e24b7ff47fd' + } + } + ] + }, + ]; +``` diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 3c841cc4d8a..87358705cb5 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,5 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, deepClone, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; @@ -132,15 +132,6 @@ export const spec = { if (validBidRequests[0].schain) { payload.schain = JSON.stringify(validBidRequests[0].schain); } - if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { - const userIds = deepClone(validBidRequests[0].userId); - - if (userIds.id5id) { - userIds.id5id = deepAccess(userIds, 'id5id.uid'); - } - - payload.userid = JSON.stringify(userIds); - } const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); if (Array.isArray(eids) && eids.length > 0) { @@ -295,22 +286,29 @@ function _findBidderRequest(bidderRequests, bidId) { } } +// This function takes all the possible sizes. +// returns string csv. function _validateSize(bid) { - if (deepAccess(bid, 'mediaTypes.video')) { - return ''; // Video bids arent allowed to override sizes via the trinity request + let size = []; + if (deepAccess(bid, 'mediaTypes.video.playerSize')) { + size.push(deepAccess(bid, 'mediaTypes.video.playerSize')) } - - if (bid.params.sizes) { - return parseSizesInput(bid.params.sizes).join(','); + if (deepAccess(bid, 'mediaTypes.video.sizes')) { + size.push(deepAccess(bid, 'mediaTypes.video.sizes')) + } + if (deepAccess(bid, 'params.sizes')) { + size.push(deepAccess(bid, 'params.sizes')); } if (deepAccess(bid, 'mediaTypes.banner.sizes')) { - return parseSizesInput(deepAccess(bid, 'mediaTypes.banner.sizes')).join(','); + size.push(deepAccess(bid, 'mediaTypes.banner.sizes')) } - - // Handle deprecated sizes definition - if (bid.sizes) { - return parseSizesInput(bid.sizes).join(','); + if (deepAccess(bid, 'sizes')) { + size.push(deepAccess(bid, 'sizes')) } + // Pass the 2d sizes array into parseSizeInput to flatten it into an array of x separated sizes. + // Then throw it into Set to uniquify it. + // Then spread it to an array again. Then join it into a csv of sizes. + return [...new Set(parseSizesInput(...size))].join(','); } function _validateSlot(bid) { diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 5059341baaa..874207adcf8 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -149,7 +149,7 @@ export const spec = { } } - const mimes = getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm']; + const mimes = getBidIdParameter('mimes', bid.params) || deepAccess(bid, 'mediaTypes.video.mimes') || ['application/javascript', 'video/mp4', 'video/webm']; const spotxReq = { id: bid.bidId, @@ -176,28 +176,29 @@ export const spec = { spotxReq.bidfloor = getBidIdParameter('price_floor', bid.params); } - if (getBidIdParameter('start_delay', bid.params) != '') { - spotxReq.video.startdelay = 0 + Boolean(getBidIdParameter('start_delay', bid.params)); + const startdelay = getBidIdParameter('start_delay', bid.params) || deepAccess(bid, 'mediaTypes.video.startdelay'); + if (startdelay) { + spotxReq.video.startdelay = 0 + Boolean(startdelay); } - if (getBidIdParameter('min_duration', bid.params) != '') { - spotxReq.video.minduration = getBidIdParameter('min_duration', bid.params); + const minduration = getBidIdParameter('min_duration', bid.params) || deepAccess(bid, 'mediaTypes.video.minduration'); + if (minduration) { + spotxReq.video.minduration = minduration; } - if (getBidIdParameter('max_duration', bid.params) != '') { - spotxReq.video.maxduration = getBidIdParameter('max_duration', bid.params); + const maxduration = getBidIdParameter('max_duration', bid.params) || deepAccess(bid, 'mediaTypes.video.maxduration'); + if (maxduration) { + spotxReq.video.maxduration = maxduration; } - if (getBidIdParameter('placement_type', bid.params) != '') { - spotxReq.video.ext.placement = getBidIdParameter('placement_type', bid.params); + const placement = getBidIdParameter('placement_type', bid.params) || deepAccess(bid, 'mediaTypes.video.placement'); + if (placement) { + spotxReq.video.ext.placement = placement; } - if (getBidIdParameter('position', bid.params) != '') { - spotxReq.video.ext.pos = getBidIdParameter('position', bid.params); - } else { - if (deepAccess(bid, 'mediaTypes.video.pos')) { - spotxReq.video.ext.pos = deepAccess(bid, 'mediaTypes.video.pos'); - } + const position = getBidIdParameter('position', bid.params) || deepAccess(bid, 'mediaTypes.video.pos'); + if (position) { + spotxReq.video.ext.pos = position; } if (bid.crumbs && bid.crumbs.pubcid) { diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 845d6b4b5fb..4ebed12cb05 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -71,7 +71,7 @@ export const spec = { seatId = bid.params.seatId; } const tagIdOrPlacementId = bid.params.tagId || bid.params.placementId; - let pos = parseInt(bid.params.pos, 10); + let pos = parseInt(bid.params.pos || deepAccess(bid.mediaTypes, 'video.pos'), 10); if (isNaN(pos)) { logWarn(`Synacormedia: there is an invalid POS: ${bid.params.pos}`); pos = 0; diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 13f04848dd6..026d5a098df 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,13 +3,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {getWindowSelf} from '../src/utils.js' +import {deepAccess, getWindowSelf, replaceAuctionPrice} from '../src/utils.js' import {getStorageManager} from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -export const END_POINT_URL = 'https://hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; +export const END_POINT_URL = 'https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction'; +export const USER_SYNC_IMG_URL = 'https://trc.taboola.com/sg/prebidJS/1/cm'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; @@ -80,8 +82,8 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; - const {publisherId, endpointUrl} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, refererInfo); + const {publisherId} = bidRequest.params; + const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); const device = {ua: navigator.userAgent}; const imps = getImps(validBidRequests); const user = { @@ -102,13 +104,19 @@ export const spec = { regs.ext.us_privacy = uspConsent; } + if (bidderRequest.ortb2?.regs?.gpp) { + regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + if (config.getConfig('coppa')) { regs.coppa = 1; } const ortb2 = bidderRequest.ortb2 || { + bcat: [], badv: [], - bcat: [] + wlang: [] }; const request = { @@ -118,13 +126,17 @@ export const spec = { device, source: {fd: 1}, tmax: bidderRequest.timeout, - bcat: ortb2.bcat, - badv: ortb2.badv, + bcat: ortb2.bcat || bidRequest.params.bcat || [], + badv: ortb2.badv || bidRequest.params.badv || [], + wlang: ortb2.wlang || bidRequest.params.wlang || [], user, - regs + regs, + ext: { + pageType: ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType + } }; - const url = [endpointUrl || END_POINT_URL, publisherId].join('/'); + const url = [END_POINT_URL, publisherId].join('/'); return { url, @@ -147,18 +159,47 @@ export const spec = { return []; } - return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); + return bidResponses.map((bidResponse) => getBid(bids, currency, bidResponse)).filter(Boolean); + }, + onBidWon: (bid) => { + if (bid.nurl) { + const resolvedNurl = replaceAuctionPrice(bid.nurl, bid.originalCpm); + ajax(resolvedNurl); + } + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = [] + const queryParams = []; + if (gdprConsent) { + queryParams.push(`gdpr=${Number(gdprConsent.gdprApplies && 1)}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString || '')}`); + } + + if (uspConsent) { + queryParams.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + if (gppConsent) { + queryParams.push('gpp=' + encodeURIComponent(gppConsent)); + } + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: USER_SYNC_IMG_URL + (queryParams.length ? '?' + queryParams.join('&') : '') + }); + } + return syncs; }, }; -function getSiteProperties({publisherId, bcat = []}, refererInfo) { +function getSiteProperties({publisherId}, refererInfo, ortb2) { const {getPageUrl, getReferrer} = internal; return { id: publisherId, name: publisherId, - domain: refererInfo?.domain || window.location?.host, - page: getPageUrl(refererInfo), - ref: getReferrer(refererInfo), + domain: ortb2?.site?.domain || refererInfo?.domain || window.location?.host, + page: ortb2?.site?.page || getPageUrl(refererInfo), + ref: ortb2?.site?.ref || getReferrer(refererInfo), publisher: { id: publisherId }, @@ -170,20 +211,39 @@ function getSiteProperties({publisherId, bcat = []}, refererInfo) { function getImps(validBidRequests) { return validBidRequests.map((bid, id) => { - const {tagId, bidfloor = null, bidfloorcur = CURRENCY} = bid.params; - - return { + const {tagId, position} = bid.params; + const imp = { id: id + 1, - banner: getBanners(bid), - tagid: tagId, - bidfloor, - bidfloorcur, - }; + banner: getBanners(bid, position), + tagid: tagId + } + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: CURRENCY, + mediaType: BANNER, + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + imp.bidfloor = parseFloat(floorInfo.floor); + imp.bidfloorcur = CURRENCY; + } + } else { + const {bidfloor = null, bidfloorcur = CURRENCY} = bid.params; + imp.bidfloor = bidfloor; + imp.bidfloorcur = bidfloorcur; + } + imp['ext'] = { + gpid: deepAccess(bid, 'ortb2Imp.ext.gpid') + } + return imp; }); } -function getBanners(bid) { - return getSizes(bid.sizes); +function getBanners(bid, pos) { + return { + ...getSizes(bid.sizes), + pos: pos + } } function getSizes(sizes) { @@ -204,7 +264,7 @@ function getBidResponses({body}) { const {seatbid, cur} = body; - if (!seatbid.length || !seatbid[0].bid) { + if (!seatbid.length || !seatbid[0].bid || !seatbid[0].bid.length) { return []; } @@ -214,14 +274,14 @@ function getBidResponses({body}) { }; } -function getBid(requestId, currency, bidResponse) { +function getBid(bids, currency, bidResponse) { if (!bidResponse) { return; } const { - price: cpm, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} + price: cpm, nurl, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} } = bidResponse; - + let requestId = bids[bidResponse.impid - 1].bidId; if (advertiserDomains && advertiserDomains.length > 0) { meta.advertiserDomains = advertiserDomains } @@ -237,6 +297,7 @@ function getBid(requestId, currency, bidResponse) { width, height, meta, + nurl, netRevenue: true }; } diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md index d63616d23a9..79538d0d48b 100644 --- a/modules/taboolaBidAdapter.md +++ b/modules/taboolaBidAdapter.md @@ -31,7 +31,6 @@ The Taboola Bidding adapter requires setup before beginning. Please contact us o bidfloor: 0.25, // Optional - default is null bcat: ['IAB1-1'], // Optional - default is [] badv: ['example.com'], // Optional - default is [] - endpointUrl: ['https://example.com'] // Optional } }] }]; @@ -46,6 +45,5 @@ The Taboola Bidding adapter requires setup before beginning. Please contact us o | `bcat` | optional | List of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | | `badv` | optional | Blocked Advertiser Domains | `'example.com'` | `String Url` | | `bidfloor` | optional | CPM bid floor | `0.25` | `Float` | -| `endpointUrl` | optional | Endpoint Url (only if provided by Taboola) | `https://example.com` | `String` | diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 11aa6c76c76..c7a54431ee9 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -11,7 +11,7 @@ const BIDDER_CODE = 'tappx'; const GVLID_CODE = 628; const TTL = 360; const CUR = 'USD'; -const TAPPX_BIDDER_VERSION = '0.1.1005'; +const TAPPX_BIDDER_VERSION = '0.1.2'; const TYPE_CNN = 'prebidjs'; const LOG_PREFIX = '[TAPPX]: '; const VIDEO_SUPPORT = ['instream', 'outstream']; @@ -271,12 +271,28 @@ function buildOneRequest(validBidRequests, bidderRequest) { api[0] = deepAccess(validBidRequests, 'params.api') ? deepAccess(validBidRequests, 'params.api') : [3, 5]; } else { let bundle = _extractPageUrl(validBidRequests, bidderRequest); - let site = {}; + let site = deepAccess(validBidRequests, 'params.site') || {}; site.name = bundle; + site.page = bidderRequest?.refererInfo?.page || deepAccess(validBidRequests, 'params.site.page') || bidderRequest?.refererInfo?.topmostLocation || window.location.href || bundle; site.domain = bundle; + site.ref = bidderRequest?.refererInfo?.ref || window.top.document.referrer || ''; + site.ext = {}; + site.ext.is_amp = bidderRequest?.refererInfo?.isAmp || 0; + site.ext.page_da = deepAccess(validBidRequests, 'params.site.page') || '-'; + site.ext.page_rip = bidderRequest?.refererInfo?.page || '-'; + site.ext.page_rit = bidderRequest?.refererInfo?.topmostLocation || '-'; + site.ext.page_wlh = window.location.href || '-'; publisher.name = bundle; publisher.domain = bundle; + let sitename = document.getElementsByTagName('meta')['title']; + if (sitename && sitename.content) { + site.name = sitename.content; + } tagid = `${site.name}_typeAdBanVid_${getOs()}`; + let keywords = document.getElementsByTagName('meta')['keywords']; + if (keywords && keywords.content) { + site.keywords = keywords.content; + } payload.site = site; } // < App/Site object @@ -295,9 +311,9 @@ function buildOneRequest(validBidRequests, bidderRequest) { if ( ((bannerMediaType.sizes[0].indexOf(480) >= 0) && (bannerMediaType.sizes[0].indexOf(320) >= 0)) || ((bannerMediaType.sizes[0].indexOf(768) >= 0) && (bannerMediaType.sizes[0].indexOf(1024) >= 0))) { - banner.pos = 7; + banner.pos = 0; } else { - banner.pos = 4; + banner.pos = 0; } banner.api = api; @@ -565,8 +581,7 @@ export function _checkParamDataType(key, value, datatype) { } export function _extractPageUrl(validBidRequests, bidderRequest) { - // TODO: does the fallback make sense? - let url = bidderRequest?.refererInfo?.page || bidderRequest.refererInfo?.topmostLocation; + let url = bidderRequest?.refererInfo?.page || bidderRequest?.refererInfo?.topmostLocation; return parseDomain(url, {noLeadingWww: true}); } diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index bc775e78647..1977686dd23 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -157,7 +157,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { const sizes = getSizes(bidRequest); const bid = { requestId: serverBid.uuid, - cpm: rtbBid.cpm * MARGIN, + cpm: rtbBid.cpm / MARGIN, creativeId: rtbBid.creative_id, dealId: rtbBid.deal_id, currency: 'USD', diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 9ce00d2d3eb..56b21d0d4cf 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -55,11 +55,13 @@ export const spec = { deviceWidth: screen.width, hb_version: '$prebid.version$', ...getSharedViewerIdParameters(validBidRequests), - ...getFirstPartyTeadsIdParameter() + ...getFirstPartyTeadsIdParameter(validBidRequests) }; - if (validBidRequests[0].schain) { - payload.schain = validBidRequests[0].schain; + const firstBidRequest = validBidRequests[0]; + + if (firstBidRequest.schain) { + payload.schain = firstBidRequest.schain; } let gdpr = bidderRequest.gdprConsent; @@ -80,6 +82,11 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } + const userAgentClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); + if (userAgentClientHints) { + payload.userAgentClientHints = userAgentClientHints; + } + const payloadString = JSON.stringify(payload); return { method: 'POST', @@ -261,14 +268,25 @@ function _validateId(id) { /** * Get the first-party cookie Teads ID parameter to be sent in bid request. + * @param validBidRequests an array of bids * @returns `{} | {firstPartyCookieTeadsId: string}` */ -function getFirstPartyTeadsIdParameter() { - if (!storage.cookiesAreEnabled()) { - return {}; +function getFirstPartyTeadsIdParameter(validBidRequests) { + const firstPartyTeadsIdFromUserIdModule = deepAccess(validBidRequests, '0.userId.teadsId'); + + if (firstPartyTeadsIdFromUserIdModule) { + return {firstPartyCookieTeadsId: firstPartyTeadsIdFromUserIdModule}; } - const firstPartyTeadsId = storage.getCookie(FP_TEADS_ID_COOKIE_NAME); - return firstPartyTeadsId ? { firstPartyCookieTeadsId: firstPartyTeadsId } : {}; + + if (storage.cookiesAreEnabled(null)) { + const firstPartyTeadsIdFromCookie = storage.getCookie(FP_TEADS_ID_COOKIE_NAME, null); + + if (firstPartyTeadsIdFromCookie) { + return {firstPartyCookieTeadsId: firstPartyTeadsIdFromCookie}; + } + } + + return {}; } registerBidder(spec); diff --git a/modules/teadsIdSystem.js b/modules/teadsIdSystem.js new file mode 100644 index 00000000000..c18f8fb76ac --- /dev/null +++ b/modules/teadsIdSystem.js @@ -0,0 +1,239 @@ +/** + * This module adds TeadsId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/teadsIdSystem + * @requires module:modules/userId + */ + +import {isStr, isNumber, logError, logInfo, isEmpty, timestamp} from '../src/utils.js' +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {uspDataHandler} from '../src/adapterManager.js'; + +const MODULE_NAME = 'teadsId'; +const GVL_ID = 132; +const FP_TEADS_ID_COOKIE_NAME = '_tfpvi'; +const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; + +export const gdprStatus = { + GDPR_DOESNT_APPLY: 0, + CMP_NOT_FOUND_OR_ERROR: 22, + GDPR_APPLIES_PUBLISHER: 12, +}; + +export const gdprReason = { + GDPR_DOESNT_APPLY: 0, + CMP_NOT_FOUND: 220, + GDPR_APPLIES_PUBLISHER_CLASSIC: 120, +}; + +export const storage = getStorageManager({gvlid: GVL_ID, moduleName: MODULE_NAME}); + +/** @type {Submodule} */ +export const teadsIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * Vendor id of Teads + * @type {number} + */ + gvlid: GVL_ID, + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{teadsId:string}} + */ + decode(value) { + return {teadsId: value} + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [submoduleConfig] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ + getId(submoduleConfig, consentData) { + const resp = function (callback) { + const url = buildAnalyticsTagUrl(submoduleConfig, consentData); + + const callbacks = { + success: (bodyResponse, responseObj) => { + if (responseObj && responseObj.status === 200) { + if (isStr(bodyResponse) && !isEmpty(bodyResponse)) { + const cookiesMaxAge = getTimestampFromDays(365); // 1 year + const expirationCookieDate = getCookieExpirationDate(cookiesMaxAge); + storage.setCookie(FP_TEADS_ID_COOKIE_NAME, bodyResponse, expirationCookieDate); + callback(bodyResponse); + } else { + storage.setCookie(FP_TEADS_ID_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); + callback(); + } + } else { + logInfo(`${MODULE_NAME}: Server error while fetching ID`); + callback(); + } + }, + error: error => { + logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + + ajax(url, callbacks, undefined, {method: 'GET'}); + }; + return {callback: resp}; + } +}; + +/** + * Build the full URL from the Submodule config & consentData + * @param submoduleConfig + * @param consentData + * @returns {string} + */ +export function buildAnalyticsTagUrl(submoduleConfig, consentData) { + const pubId = getPublisherId(submoduleConfig); + const teadsViewerId = getTeadsViewerId(); + const status = getGdprStatus(consentData); + const gdprConsentString = getGdprConsentString(consentData); + const ccpaConsentString = getCcpaConsentString(uspDataHandler?.getConsentData()); + const gdprReason = getGdprReasonFromStatus(status); + const params = { + analytics_tag_id: pubId, + tfpvi: teadsViewerId, + gdpr_consent: gdprConsentString, + gdpr_status: status, + gdpr_reason: gdprReason, + ccpa_consent: ccpaConsentString, + sv: 'prebid-v1', + }; + + const url = 'https://at.teads.tv/fpc'; + const queryParams = new URLSearchParams(); + + for (const param in params) { + queryParams.append(param, params[param]); + } + + return url + '?' + queryParams.toString(); +} + +/** + * Extract the Publisher ID from the Submodule config + * @returns {string} + * @param submoduleConfig + */ +export function getPublisherId(submoduleConfig) { + const pubId = submoduleConfig?.params?.pubId; + const prefix = 'PUB_'; + if (isNumber(pubId)) { + return prefix + pubId.toString(); + } + if (isStr(pubId) && parseInt(pubId)) { + return prefix + pubId; + } + return ''; +} + +/** + * Extract the GDPR status from the given consentData + * @param consentData + * @returns {number} + */ +export function getGdprStatus(consentData) { + const gdprApplies = consentData?.gdprApplies; + if (gdprApplies === true) { + return gdprStatus.GDPR_APPLIES_PUBLISHER; + } else if (gdprApplies === false) { + return gdprStatus.GDPR_DOESNT_APPLY; + } else { + return gdprStatus.CMP_NOT_FOUND_OR_ERROR; + } +} + +/** + * Extract the GDPR consent string from the given consentData + * @param consentData + * @returns {string} + */ +export function getGdprConsentString(consentData) { + const consentString = consentData?.consentString; + if (isStr(consentString)) { + return consentString; + } else { + return ''; + } +} + +/** + * Map the GDPR reason from the given GDPR status + * @param status + * @returns {number} + */ +function getGdprReasonFromStatus(status) { + switch (status) { + case gdprStatus.GDPR_DOESNT_APPLY: + return gdprReason.GDPR_DOESNT_APPLY; + case gdprStatus.CMP_NOT_FOUND_OR_ERROR: + return gdprReason.CMP_NOT_FOUND; + case gdprStatus.GDPR_APPLIES_PUBLISHER: + return gdprReason.GDPR_APPLIES_PUBLISHER_CLASSIC; + default: + return -1; + } +} + +/** + * Return the well formatted CCPA consent string + * @param ccpaConsentString + * @returns {string|*} + */ +export function getCcpaConsentString(ccpaConsentString) { + if (isStr(ccpaConsentString)) { + return ccpaConsentString; + } else { + return ''; + } +} + +/** + * Get the cookie expiration date string from a given Date and a max age + * @param {number} maxAge + * @returns {string} + */ +export function getCookieExpirationDate(maxAge) { + return new Date(timestamp() + maxAge).toUTCString() +} + +/** + * Get cookie from Cookie or Local Storage + * @returns {string} + */ +function getTeadsViewerId() { + const teadsViewerId = readCookie() + if (isStr(teadsViewerId)) { + return teadsViewerId + } else { + return ''; + } +} + +function readCookie() { + return storage.cookiesAreEnabled(null) ? storage.getCookie(FP_TEADS_ID_COOKIE_NAME, null) : null; +} + +/** + * Return a number of milliseconds from given days number + * @param days + * @returns {number} + */ +export function getTimestampFromDays(days) { + return days * 24 * 60 * 60 * 1000; +} +submodule('userId', teadsIdSubmodule); diff --git a/modules/teadsIdSystem.md b/modules/teadsIdSystem.md new file mode 100644 index 00000000000..b898ceaeacf --- /dev/null +++ b/modules/teadsIdSystem.md @@ -0,0 +1,22 @@ +# Overview + +Module Name: Teads Id System +Module Type: User Id System +Maintainer: innov-ssp@teads.com + +# Description + +Teads user identification system. GDPR & CCPA compliant. + +## Example configuration for publishers: + + pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'teadsId', + params: { + pubId: 1234 + } + }] + } + }); diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index dbd1d3e90e9..f5a3c0a2c70 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -1,8 +1,31 @@ -import {logError, logWarn, mergeDeep} from '../src/utils.js'; +import {logError, logWarn, mergeDeep, isEmpty, safeJSONParse, logInfo, hasDeviceAccess} from '../src/utils.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {submodule} from '../src/hook.js'; import {GreedyPromise} from '../src/utils/promise.js'; +import {config} from '../src/config.js'; +import {getCoreStorageManager} from '../src/storageManager.js'; +import {includes} from '../src/polyfill.js'; +import {gdprDataHandler} from '../src/adapterManager.js'; +const MODULE_NAME = 'topicsFpd'; +const DEFAULT_EXPIRATION_DAYS = 21; +const TCF_REQUIRED_PURPOSES = ['1', '2', '3', '4']; +let HAS_GDPR_CONSENT = true; +let LOAD_TOPICS_INITIALISE = false; +const HAS_DEVICE_ACCESS = hasDeviceAccess(); + +const bidderIframeList = { + maxTopicCaller: 1, + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' + }] +} +export const coreStorage = getCoreStorageManager(MODULE_NAME); +export const topicStorageName = 'prebid:topics'; +export const lastUpdated = 'lastUpdated'; + +const iframeLoadedURL = []; const TAXONOMIES = { // map from topic taxonomyVersion to IAB segment taxonomy '1': 600 @@ -17,6 +40,20 @@ function partitionBy(field, items) { }, {}); } +/** + * function to get list of loaded Iframes calling Topics API + */ +function getLoadedIframeURL() { + return iframeLoadedURL; +} + +/** + * function to set/push iframe in the list which is loaded to called topics API. + */ +function setLoadedIframeURL(url) { + return iframeLoadedURL.push(url); +} + export function getTopicsData(name, topics, taxonomies = TAXONOMIES) { return Object.entries(partitionBy('taxonomyVersion', topics)) .filter(([taxonomyVersion]) => { @@ -61,7 +98,12 @@ export function getTopics(doc = document) { const topicsData = getTopics().then((topics) => getTopicsData(getRefererInfo().domain, topics)); export function processFpd(config, {global}, {data = topicsData} = {}) { + if (!LOAD_TOPICS_INITIALISE) { + loadTopicsForBidders(); + LOAD_TOPICS_INITIALISE = true; + } return data.then((data) => { + data = [].concat(data, getCachedTopics()); // Add cached data in FPD data. if (data.length) { mergeDeep(global, { user: { @@ -73,6 +115,145 @@ export function processFpd(config, {global}, {data = topicsData} = {}) { }); } +/** + * function to fetch the cached topic data from storage for bidders and return it + */ +export function getCachedTopics() { + let cachedTopicData = []; + if (!HAS_GDPR_CONSENT || !HAS_DEVICE_ACCESS) { + return cachedTopicData; + } + const topics = config.getConfig('userSync.topics') || bidderIframeList; + const bidderList = topics.bidders || []; + let storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); + storedSegments && storedSegments.forEach((value, cachedBidder) => { + // Check bidder exist in config for cached bidder data and then only retrieve the cached data + let bidderConfigObj = bidderList.find(({bidder}) => cachedBidder == bidder) + if (bidderConfigObj) { + if (!isCachedDataExpired(value[lastUpdated], bidderConfigObj?.expiry || DEFAULT_EXPIRATION_DAYS)) { + Object.keys(value).forEach((segData) => { + segData != lastUpdated && cachedTopicData.push(value[segData]); + }) + } else { + // delete the specific bidder map from the store and store the updated maps + storedSegments.delete(cachedBidder); + coreStorage.setDataInLocalStorage(topicStorageName, JSON.stringify([...storedSegments])); + } + } + }); + return cachedTopicData; +} + +/** + * Recieve messages from iframe loaded for bidders to fetch topic + * @param {MessageEvent} evt + */ +export function receiveMessage(evt) { + if (evt && evt.data) { + try { + let data = safeJSONParse(evt.data); + if (includes(getLoadedIframeURL(), evt.origin) && data && data.segment && !isEmpty(data.segment.topics)) { + const {domain, topics, bidder} = data.segment; + const iframeTopicsData = getTopicsData(domain, topics)[0]; + iframeTopicsData && storeInLocalStorage(bidder, iframeTopicsData); + } + } catch (err) { } + } +} + +/** +Function to store Topics data recieved from iframe in storage(name: "prebid:topics") +* @param {Topics} topics +*/ +export function storeInLocalStorage(bidder, topics) { + const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); + if (storedSegments.has(bidder)) { + storedSegments.get(bidder)[topics['ext']['segclass']] = topics; + storedSegments.get(bidder)[lastUpdated] = new Date().getTime(); + storedSegments.set(bidder, storedSegments.get(bidder)); + } else { + storedSegments.set(bidder, {[topics.ext.segclass]: topics, [lastUpdated]: new Date().getTime()}) + } + coreStorage.setDataInLocalStorage(topicStorageName, JSON.stringify([...storedSegments])); +} + +function isCachedDataExpired(storedTime, cacheTime) { + const _MS_PER_DAY = 1000 * 60 * 60 * 24; + const currentTime = new Date().getTime(); + const daysDifference = Math.ceil((currentTime - storedTime) / _MS_PER_DAY); + return daysDifference > cacheTime; +} + +/** +* Function to get random bidders based on count passed with array of bidders +**/ +function getRandomBidders(arr, count) { + return ([...arr].sort(() => 0.5 - Math.random())).slice(0, count) +} + +/** + * function to add listener for message receiving from IFRAME + */ +function listenMessagesFromTopicIframe() { + window.addEventListener('message', receiveMessage, false); +} + +function checkTCFv2(vendorData, requiredPurposes = TCF_REQUIRED_PURPOSES) { + const {gdprApplies, purpose} = vendorData; + if (!gdprApplies || !purpose) { + return true; + } + return requiredPurposes.map((purposeNo) => { + const purposeConsent = purpose.consents ? purpose.consents[purposeNo] : false; + if (purposeConsent) { + return true; + } + return false; + }).reduce((a, b) => a && b, true); +} + +export function hasGDPRConsent() { + // Check for GDPR consent for purpose 1,2,3,4 and return false if consent has not been given + const gdprConsent = gdprDataHandler.getConsentData(); + const hasGdpr = (gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? gdprConsent.consentString : ''; + if (hasGdpr) { + if ((!gdprConsentString || gdprConsentString === '') || !gdprConsent.vendorData) { + return false; + } + return checkTCFv2(gdprConsent.vendorData); + } + return true; +} + +/** + * function to load the iframes of the bidder to load the topics data + */ +function loadTopicsForBidders() { + HAS_GDPR_CONSENT = hasGDPRConsent(); + if (!HAS_GDPR_CONSENT || !HAS_DEVICE_ACCESS) { + logInfo('Topics Module : Consent string is required to fetch the topics from third party domains.'); + return; + } + const topics = config.getConfig('userSync.topics') || bidderIframeList; + if (topics) { + listenMessagesFromTopicIframe(); + const randomBidders = getRandomBidders(topics.bidders || [], topics.maxTopicCaller || 1) + randomBidders && randomBidders.forEach(({ bidder, iframeURL }) => { + if (bidder && iframeURL) { + let ifrm = document.createElement('iframe'); + ifrm.name = 'ifrm_'.concat(bidder); + ifrm.src = ''.concat(iframeURL, '?bidder=').concat(bidder); + ifrm.style.display = 'none'; + setLoadedIframeURL(new URL(iframeURL).origin); + iframeURL && window.document.documentElement.appendChild(ifrm); + } + }) + } else { + logWarn(`Topics config not defined under userSync Object`); + } +} + submodule('firstPartyData', { name: 'topics', queue: 1, diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md new file mode 100644 index 00000000000..b1bc3cb0d5b --- /dev/null +++ b/modules/topicsFpdModule.md @@ -0,0 +1,62 @@ +# Overview + +Module Name: topicsFpdModule + +# Description +Purpose of this module is to call the Topics API (document.browsingTopics()) which will fetch the first party domain as well third party domain(Iframe) topics data which will be sent onto user.data in bid stream. + +The intent of the Topics API is to provide callers (including third-party ad-tech or advertising providers on the page that run script) with coarse-grained advertising topics that the page visitor might currently be interested in. + +Topics Module(topicsFpdModule) should be included in prebid final package to call topics API. +Module topicsFpdModule helps to call the Topics API which will send topics data in bid stream (onto user.data) + +``` +try { + if ('browsingTopics' in document && document.featurePolicy.allowsFeature('browsing-topics')) { + topics = document.browsingTopics(); + } +} catch (e) { + console.error('Could not call topics API', e); +} +``` + +# Topics Iframe Configuration + +Topics iframe implementation is the enhancements of existing module under topicsFpdModule.js where different bidders will call the topic API under their domain to fetch the topics for respective domain and the segment data will be part of ORTB request under user.data object. Default config is maintained in the module itself. + +Below are the configuration which can be used to configure and override the default config maintained in the module. + +``` +pbjs.setConfig({ + userSync: { + ..., + topics: { + maxTopicCaller: 3, // SSP rotation + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html', + expiry: 7 // Configurable expiry days + },{ + bidder: 'rubicon', + iframeURL: 'https://rubicon.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + },{ + bidder: 'appnexus', + iframeURL: 'https://appnexus.com:8080/topics/fpd/topic.html', // dummy URL + expiry: 7 // Configurable expiry days + }] + } + .... + } +}) +``` + +## Topics Config Descriptions + +| Field | Required? | Type | Description | +|---|---|---|---| +| topics.maxTopicCaller | no | integer | Defines the maximum numbers of Bidders Iframe which needs to be loaded on the publisher page. Default is 1 which is hardcoded in Module. Eg: topics.maxTopicCaller is set to 3. If there are 10 bidders configured along with their iframe URLS, random 3 bidders iframe URL is loaded which will call TOPICS API. If topics.maxTopicCaller is set to 0, it will load random 1(default) bidder iframe atleast. | +| topics.bidders | no | Array of objects | Array of topics callers with the iframe locations and other necessary informations like bidder(Bidder code) and expiry. Default Array of topics in the module itself.| +| topics.bidders[].bidder | yes | string | Bidder Code of the bidder(SSP). | +| topics.bidders[].iframeURL | yes | string | URL which is hosted on bidder/SSP/third-party domains which will call Topics API. | +| topics.bidders[].expiry | no | integer | Max number of days where Topics data will be persist. If Data is stored for more than mentioned expiry day, it will be deleted from storage. Default is 21 days which is hardcoded in Module. | \ No newline at end of file diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 7f6ec90c7b9..8012a4a8051 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -154,6 +154,10 @@ function _buildPostBody(bidRequests, bidderRequest) { if (!isEmpty(ext)) { data.ext = ext; } + + if (bidderRequest?.ortb2?.regs?.gpp) { + data.regs = Object.assign({}, bidderRequest.ortb2.regs); + } return data; } @@ -432,6 +436,10 @@ function _buildResponseObject(bidderRequest, bid) { if (bid.tl_source && bid.tl_source == 'tlx') { bidResponse.meta.mediaType = 'native'; } + + if (creativeId) { + bidResponse.meta.networkId = creativeId.slice(0, creativeId.indexOf('_')); + } }; return bidResponse; } diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 882f7e58960..884c43c438a 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -38,6 +38,10 @@ function getRegs(bidderRequest) { if (config.getConfig('coppa') === true) { regs.coppa = 1; } + if (bidderRequest.ortb2?.regs) { + utils.mergeDeep(regs, bidderRequest.ortb2.regs); + } + return regs; } @@ -121,17 +125,26 @@ function getUser(bidderRequest) { utils.deepSetValue(user, 'ext.eids', eids); } + // gather user.data + const ortb2UserData = utils.deepAccess(bidderRequest, 'ortb2.user.data'); + if (ortb2UserData && ortb2UserData.length) { + user = utils.mergeDeep(user, { + data: [...ortb2UserData] + }); + }; return user; } function getSite(bidderRequest, firstPartyData) { - var site = { + var site = utils.mergeDeep({ page: utils.deepAccess(bidderRequest, 'refererInfo.page'), + ref: utils.deepAccess(bidderRequest, 'refererInfo.ref'), publisher: { id: utils.deepAccess(bidderRequest, 'bids.0.params.publisherId'), }, - ...firstPartyData.site - }; + }, + firstPartyData.site + ); var publisherDomain = bidderRequest.refererInfo.domain; if (publisherDomain) { diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 23656639532..9fd75f12591 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /** * This module adds uid2 ID support to the User ID module * The {@link module:modules/userId} module is required. @@ -10,48 +11,142 @@ import {submodule} from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; const MODULE_NAME = 'uid2'; +const MODULE_REVISION = `1.0`; +const PREBID_VERSION = '$prebid.version$'; +const UID2_CLIENT_ID = `PrebidJS-${PREBID_VERSION}-UID2Module-${MODULE_REVISION}`; const GVLID = 887; const LOG_PRE_FIX = 'UID2: '; const ADVERTISING_COOKIE = '__uid2_advertising_token'; -function readCookie() { - return storage.cookiesAreEnabled() ? storage.getCookie(ADVERTISING_COOKIE) : null; +// eslint-disable-next-line no-unused-vars +const UID2_TEST_URL = 'https://operator-integ.uidapi.com'; +const UID2_PROD_URL = 'https://prod.uidapi.com'; +const UID2_BASE_URL = UID2_PROD_URL; + +function getStorage() { + return getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +} + +function createLogInfo(prefix) { + return function (...strings) { + logInfo(prefix + ' ', ...strings); + } } +export const storage = getStorage(); +const _logInfo = createLogInfo(LOG_PRE_FIX); function readFromLocalStorage() { return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(ADVERTISING_COOKIE) : null; } -function getStorage() { - return getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +function readModuleCookie() { + const cookie = readCookie(ADVERTISING_COOKIE); + if (cookie && cookie.includes('{')) { + return JSON.parse(cookie); + } + return cookie; } -const storage = getStorage(); +function readJsonCookie(cookieName) { + return JSON.parse(readCookie(cookieName)); +} -const _logInfo = createLogInfo(LOG_PRE_FIX); +function readCookie(cookieName) { + const cookie = storage.cookiesAreEnabled() ? storage.getCookie(cookieName) : null; + if (!cookie) { + _logInfo(`Attempted to read UID2 from cookie '${cookieName}' but it was empty`); + return null; + }; + _logInfo(`Read UID2 from cookie '${cookieName}'`); + return cookie; +} -function createLogInfo(prefix) { - return function (...strings) { - logInfo(prefix + ' ', ...strings); +function storeValue(value) { + if (storage.cookiesAreEnabled()) { + storage.setCookie(ADVERTISING_COOKIE, JSON.stringify(value), Date.now() + 60 * 60 * 24 * 1000); + } else if (storage.localStorageIsEnabled()) { + storage.setLocalStorage(ADVERTISING_COOKIE, value); } } -/** - * Encode the id - * @param value - * @returns {string|*} - */ -function encodeId(value) { - const result = {}; - if (value) { - const bidIds = { - id: value +function isValidIdentity(identity) { + return !!(typeof identity === 'object' && identity !== null && identity.advertising_token && identity.identity_expires && identity.refresh_from && identity.refresh_token && identity.refresh_expires); +} + +// This is extracted from an in-progress API client. Once it's available via NPM, this class should be replaced with the NPM package. +class Uid2ApiClient { + constructor(opts) { + this._baseUrl = opts.baseUrl ? opts.baseUrl : UID2_BASE_URL; + this._clientVersion = UID2_CLIENT_ID; + } + createArrayBuffer(text) { + const arrayBuffer = new Uint8Array(text.length); + for (let i = 0; i < text.length; i++) { + arrayBuffer[i] = text.charCodeAt(i); } - result.uid2 = bidIds; - _logInfo('Decoded value ' + JSON.stringify(result)); - return result; + return arrayBuffer; + } + hasStatusResponse(response) { + return typeof (response) === 'object' && response && response.status; + } + isValidRefreshResponse(response) { + return this.hasStatusResponse(response) && ( + response.status === 'optout' || response.status === 'expired_token' || (response.status === 'success' && response.body && isValidIdentity(response.body)) + ); + } + ResponseToRefreshResult(response) { + if (this.isValidRefreshResponse(response)) { + if (response.status === 'success') { return { status: response.status, identity: response.body }; } + return response; + } else { return "Response didn't contain a valid status"; } + } + callRefreshApi(refreshDetails) { + const url = this._baseUrl + '/v2/token/refresh'; + const req = new XMLHttpRequest(); + req.overrideMimeType('text/plain'); + req.open('POST', url, true); + req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); + let resolvePromise; + let rejectPromise; + const promise = new Promise((resolve, reject) => { + resolvePromise = resolve; + rejectPromise = reject; + }); + req.onreadystatechange = () => { + if (req.readyState !== req.DONE) { return; } + try { + if (!refreshDetails.refresh_response_key || req.status !== 200) { + _logInfo('Error status OR no response decryption key available, assuming unencrypted JSON'); + const response = JSON.parse(req.responseText); + const result = this.ResponseToRefreshResult(response); + if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + } else { + _logInfo('Decrypting refresh API response'); + const encodeResp = this.createArrayBuffer(atob(req.responseText)); + window.crypto.subtle.importKey('raw', this.createArrayBuffer(atob(refreshDetails.refresh_response_key)), { name: 'AES-GCM' }, false, ['decrypt']).then((key) => { + _logInfo('Imported decryption key') + // returns the symmetric key + window.crypto.subtle.decrypt({ + name: 'AES-GCM', + iv: encodeResp.slice(0, 12), + tagLength: 128, // The tagLength you used to encrypt (if any) + }, key, encodeResp.slice(12)).then((decrypted) => { + const decryptedResponse = String.fromCharCode(...new Uint8Array(decrypted)); + _logInfo('Decrypted to:', decryptedResponse); + const response = JSON.parse(decryptedResponse); + const result = this.ResponseToRefreshResult(response); + if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + }, (reason) => console.warn(`Call to UID2 API failed`, reason)); + }, (reason) => console.warn(`Call to UID2 API failed`, reason)); + } + } catch (err) { + rejectPromise(err); + } + }; + _logInfo('Sending refresh request', req); + req.send(refreshDetails.refresh_token); + return promise; } - return undefined; } /** @type {Submodule} */ @@ -71,27 +166,118 @@ export const uid2IdSubmodule = { * decode the stored id value for passing to bid requests * @function * @param {string} value - * @returns {{uid2:{ id: string }} or undefined if value doesn't exists + * @returns {{uid2:{ id: string } }} or undefined if value doesn't exists */ decode(value) { - return (value) ? encodeId(value) : undefined; + const result = decodeImpl(value); + _logInfo('UID2 decode returned', result); + return result; }, /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleConfig} [config] + * @param {SubmoduleConfig} [configparams] * @param {ConsentData|undefined} consentData * @returns {uid2Id} */ getId(config, consentData) { - _logInfo('Creating UID 2.0'); - let value = readCookie() || readFromLocalStorage(); - _logInfo('The advertising token: ' + value); - return {id: value} + const result = getIdImpl(config, consentData); + _logInfo(`UID2 getId returned`, result); + return result; }, - }; +function refreshTokenAndStore(baseUrl, token) { + _logInfo('UID2 base url provided: ', baseUrl); + const client = new Uid2ApiClient({baseUrl}); + return client.callRefreshApi(token).then((response) => { + _logInfo('Refresh endpoint responded with:', response); + const tokens = { + originalToken: token, + latestToken: response.identity, + }; + storeValue(tokens); + return tokens; + }); +} + +function decodeImpl(value) { + if (typeof value === 'string') { + _logInfo('Found an old-style ID from an earlier version of the module. Refresh is unavailable for this token.'); + const result = { uid2: { id: value } }; + return result; + } + if (Date.now() < value.latestToken.identity_expires) { + return { uid2: { id: value.latestToken.advertising_token } }; + } + return null; +} + +function getIdImpl(config, consentData) { + let suppliedToken = null; + const uid2BaseUrl = config?.params?.uid2ApiBase ?? UID2_BASE_URL; + if (config && config.params) { + if (config.params.uid2Token) { + suppliedToken = config.params.uid2Token; + _logInfo('Read token from params', suppliedToken); + } else if (config.params.uid2ServerCookie) { + suppliedToken = readJsonCookie(config.params.uid2ServerCookie); + _logInfo('Read token from server-supplied cookie', suppliedToken); + } + } + let storedTokens = readModuleCookie() || readFromLocalStorage(); + _logInfo('Loaded module-stored tokens:', storedTokens); + + if (storedTokens && typeof storedTokens === 'string') { + // Legacy value stored, this must be from an old integration. If no token supplied, just use the legacy value. + + if (!suppliedToken) { + _logInfo('Returning legacy cookie value.'); + return { id: storedTokens }; + } + // Otherwise, ignore the legacy value - it should get over-written later anyway. + _logInfo('Discarding superseded legacy cookie.'); + storedTokens = null; + } + + if (suppliedToken && storedTokens) { + if (storedTokens.originalToken?.advertising_token !== suppliedToken.advertising_token) { + _logInfo('Server supplied new token - ignoring stored value.', storedTokens.originalToken?.advertising_token, suppliedToken.advertising_token); + // Stored token wasn't originally sourced from the provided token - ignore the stored value. A new user has logged in? + storedTokens = null; + } + } + // At this point, any legacy values or superseded stored tokens have been nulled out. + const useSuppliedToken = !(storedTokens?.latestToken) || (suppliedToken && suppliedToken.identity_expires > storedTokens.latestToken.identity_expires); + const newestAvailableToken = useSuppliedToken ? suppliedToken : storedTokens.latestToken; + _logInfo('UID2 module selected latest token', useSuppliedToken, newestAvailableToken); + if (!newestAvailableToken || Date.now() > newestAvailableToken.refresh_expires) { + _logInfo('Newest available token is expired and not refreshable.'); + return { id: null }; + } + if (Date.now() > newestAvailableToken.identity_expires) { + const promise = refreshTokenAndStore(uid2BaseUrl, newestAvailableToken); + _logInfo('Token is expired but can be refreshed, attempting refresh.'); + return { callback: (cb) => { + promise.then((result) => { + _logInfo('Refresh reponded, passing the updated token on.', result); + cb(result); + }); + } }; + } + // If should refresh (but don't need to), refresh in the background. + if (Date.now() > newestAvailableToken.refresh_from) { + _logInfo(`Refreshing token in background with low priority.`); + refreshTokenAndStore(uid2BaseUrl, newestAvailableToken); + } + const tokens = { + originalToken: suppliedToken ?? storedTokens?.originalToken, + latestToken: newestAvailableToken, + }; + storeValue(tokens); + return { id: tokens }; +} + // Register submodule for userId submodule('userId', uid2IdSubmodule); diff --git a/modules/uid2IdSystem.md b/modules/uid2IdSystem.md index fa596b17584..82ff1b3cb68 100644 --- a/modules/uid2IdSystem.md +++ b/modules/uid2IdSystem.md @@ -6,11 +6,23 @@ UID 2.0 ID Module. Individual params may be set for the UID 2.0 Submodule. At least one identifier must be set in the params. +The module will handle refreshing the token periodically and storing the updated token using the Prebid.js storage manager. If you provide an expired identity and the module has a valid identity which was refreshed from the identity you provide, it will use the refreshed identity. The module stores the original token used for refreshing the token, and it will use the refreshed tokens as long as the original token matches the one supplied. + ``` pbjs.setConfig({ userSync: { userIds: [{ - name: 'uid2' + name: 'uid2', + params: { + // Either: + uid2ServerCookie: 'your_UID2_server_set_cookie_name' + // Or: + uid2Token: { + 'advertising_token': '...', + 'refresh_token': '...', + // etc. - see the Sample Token below for contents of this object + } + } }] } }); @@ -18,7 +30,25 @@ pbjs.setConfig({ ## Parameter Descriptions for the `usersync` Configuration Section The below parameters apply only to the UID 2.0 User ID Module integration. +You should supply either `uid2Token` or `uid2ServerCookie`. + +If you provide `uid2Token`, the value should be a JavaScript/JSON object with the decrypted `body` payload response from a call to either `/token/generate` or `/token/refresh`. + +If you provide `uid2ServerCookie`, the module will expect that same JSON object to be stored in the cookie - i.e. it will pass the cookie value to `JSON.parse` and expect to receive an object containing similar to what you see in the `Sample token` section below. + +The module will make calls to the `/token/refresh` endpoint to update the token it stores internally, so bids may contain an updated token. + +If neither of `uid2Token` or `uid2ServerCookie` are supplied, and the module has stored a token using the Prebid.js storage system (typically in a cookie named `__uid2_advertising_token`), it will use that token. This cookie is internal to the module and should not be set directly. + +If a new token is supplied which does not match the original token used to generate any refreshed tokens, all stored tokens will be discarded and the new token used instead (refreshed if necessary). + +### Sample token + +`{`
  `"advertising_token": "...",`
  `"refresh_token": "...",`
  `"identity_expires": 1633643601000,`
  `"refresh_from": 1633643001000,`
  `"refresh_expires": 1636322000000,`
  `"refresh_response_key": "wR5t6HKMfJ2r4J7fEGX9Gw=="`
`}` + | Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | ID value for the UID20 module - `"uid2"` | `"uid2"` | -| value | Optional | Object | Used only if the page has a separate mechanism for storing the UID 2.0 ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"uid2": { "id": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}}` | +| params.uid2Token | Optional | Object | The initial UID2 token. This should be `body` element of the decrypted response from a call to the `/token/generate` or `/token/refresh` endpoint. | See the sample token above. | +| params.uid2ServerCookie | Optional | String | The name of a cookie which holds the initial UID2 token, set by the server. The cookie should contain JSON in the same format as the alternative uid2Token param. **If uid2Token is supplied, this param is ignored.** | See the sample token above. | +| params.uid2ApiBase | Optional | String | Overrides the default UID2 API endpoint. | `https://prod.uidapi.com` _(default)_ | diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 23dc9809041..199168e12bb 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -88,13 +88,13 @@ export const USER_IDS_CONFIG = { atype: 1, getValue: function(data) { let value = ''; - if (data.DeviceID) { - value = data.DeviceID.join(','); + if (data && data.ext && data.ext.DeviceID) { + value = data.ext.DeviceID; } return value; }, getUidExt: function(data) { - return 'DeviceID'; + return data && data.ext; } }, @@ -282,7 +282,7 @@ export const USER_IDS_CONFIG = { }, amxId: { - source: 'amxrtb.com', + source: 'amxdt.net', atype: 1, }, @@ -418,18 +418,6 @@ export function createEidsArray(bidRequestUserId) { if (bidRequestUserId.hasOwnProperty(subModuleKey)) { if (subModuleKey === 'pubProvidedId') { eids = eids.concat(bidRequestUserId['pubProvidedId']); - } else if (subModuleKey === 'ftrackId') { - // ftrack has multiple IDs so we add each one that exists - let eid = { - 'atype': 1, - 'id': (bidRequestUserId.ftrackId.DeviceID || []).join('|'), - 'ext': {} - } - for (let id in bidRequestUserId.ftrackId) { - eid.ext[id] = (bidRequestUserId.ftrackId[id] || []).join('|'); - } - - eids.push(eid); } else if (Array.isArray(bidRequestUserId[subModuleKey])) { bidRequestUserId[subModuleKey].forEach((config, index, arr) => { const eid = createEidObject(config, subModuleKey); @@ -446,6 +434,7 @@ export function createEidsArray(bidRequestUserId) { } } } + return eids; } diff --git a/modules/userId/index.js b/modules/userId/index.js index ca4a67019ec..82ebaf2b14e 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -129,7 +129,7 @@ import {find, includes} from '../../src/polyfill.js'; import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; import {getGlobal} from '../../src/prebidGlobal.js'; -import {gdprDataHandler} from '../../src/adapterManager.js'; +import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; import {hook, module, ready as hooksReady} from '../../src/hook.js'; import {buildEidPermissions, createEidsArray, USER_IDS_CONFIG} from './eids.js'; @@ -148,13 +148,14 @@ import { logError, logInfo, logWarn, - timestamp, - isEmpty + isEmpty, deepSetValue } from '../../src/utils.js'; import {getPPID as coreGetPPID} from '../../src/adserver.js'; import {defer, GreedyPromise} from '../../src/utils/promise.js'; import {hasPurpose1Consent} from '../../src/utils/gpdr.js'; +import {registerOrtbProcessor, REQUEST} from '../../src/pbjsORTB.js'; import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetrics.js'; +import {findRootDomain} from '../../src/fpd/rootDomain.js'; const MODULE_NAME = 'User ID'; const COOKIE = 'cookie'; @@ -216,6 +217,14 @@ export function setSubmoduleRegistry(submodules) { submoduleRegistry = submodules; } +function cookieSetter(submodule) { + const domainOverride = (typeof submodule.submodule.domainOverride === 'function') ? submodule.submodule.domainOverride() : null; + const name = submodule.config.storage.name; + return function setCookie(suffix, value, expiration) { + coreStorage.setCookie(name + (suffix || ''), value, expiration, 'Lax', domainOverride); + } +} + /** * @param {SubmoduleContainer} submodule * @param {(Object|string)} value @@ -225,15 +234,15 @@ export function setStoredValue(submodule, value) { * @type {SubmoduleStorage} */ const storage = submodule.config.storage; - const domainOverride = (typeof submodule.submodule.domainOverride === 'function') ? submodule.submodule.domainOverride() : null; try { - const valueStr = isPlainObject(value) ? JSON.stringify(value) : value; const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); + const valueStr = isPlainObject(value) ? JSON.stringify(value) : value; if (storage.type === COOKIE) { - coreStorage.setCookie(storage.name, valueStr, expiresStr, 'Lax', domainOverride); + const setCookie = cookieSetter(submodule); + setCookie(null, valueStr, expiresStr); if (typeof storage.refreshInSeconds === 'number') { - coreStorage.setCookie(`${storage.name}_last`, new Date().toUTCString(), expiresStr, 'Lax', domainOverride); + setCookie('_last', new Date().toUTCString(), expiresStr); } } else if (storage.type === LOCAL_STORAGE) { coreStorage.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); @@ -247,6 +256,31 @@ export function setStoredValue(submodule, value) { } } +export function deleteStoredValue(submodule) { + let deleter, suffixes; + switch (submodule.config?.storage?.type) { + case COOKIE: + const setCookie = cookieSetter(submodule); + const expiry = (new Date(Date.now() - 1000 * 60 * 60 * 24)).toUTCString(); + deleter = (suffix) => setCookie(suffix, '', expiry) + suffixes = ['', '_last']; + break; + case LOCAL_STORAGE: + deleter = (suffix) => coreStorage.removeDataFromLocalStorage(submodule.config.storage.name + suffix) + suffixes = ['', '_last', '_exp']; + break; + } + if (deleter) { + suffixes.forEach(suffix => { + try { + deleter(suffix) + } catch (e) { + logError(e); + } + }); + } +} + function setPrebidServerEidPermissions(initializedSubmodules) { let setEidPermissions = getPrebidInternal().setEidPermissions; if (typeof setEidPermissions === 'function' && isArray(initializedSubmodules)) { @@ -351,60 +385,6 @@ function storedConsentDataMatchesConsentData(storedConsentData, consentData) { ); } -/** - * Find the root domain - * @param {string|undefined} fullDomain - * @return {string} - */ -export function findRootDomain(fullDomain = window.location.hostname) { - if (!coreStorage.cookiesAreEnabled()) { - return fullDomain; - } - - const domainParts = fullDomain.split('.'); - if (domainParts.length == 2) { - return fullDomain; - } - let rootDomain; - let continueSearching; - let startIndex = -2; - const TEST_COOKIE_NAME = `_rdc${Date.now()}`; - const TEST_COOKIE_VALUE = 'writeable'; - do { - rootDomain = domainParts.slice(startIndex).join('.'); - let expirationDate = new Date(timestamp() + 10 * 1000).toUTCString(); - - // Write a test cookie - coreStorage.setCookie( - TEST_COOKIE_NAME, - TEST_COOKIE_VALUE, - expirationDate, - 'Lax', - rootDomain, - undefined - ); - - // See if the write was successful - const value = coreStorage.getCookie(TEST_COOKIE_NAME, undefined); - if (value === TEST_COOKIE_VALUE) { - continueSearching = false; - // Delete our test cookie - coreStorage.setCookie( - TEST_COOKIE_NAME, - '', - 'Thu, 01 Jan 1970 00:00:01 GMT', - undefined, - rootDomain, - undefined - ); - } else { - startIndex += -1; - continueSearching = Math.abs(startIndex) <= domainParts.length; - } - } while (continueSearching); - return rootDomain; -} - /** * @param {SubmoduleContainer[]} submodules * @param {function} cb - callback for after processing is done. @@ -425,6 +405,7 @@ function processSubmoduleCallbacks(submodules, cb) { } // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.submodule.decode(idObj, submodule.config); + updatePPID(submodule.idObj); } else { logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } @@ -613,9 +594,9 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { let initIdSystem; -function getPPID() { +function getPPID(eids = getUserIdsAsEids() || []) { // userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com - const matchingUserId = ppidSource && (getUserIdsAsEids() || []).find(userID => userID.source === ppidSource); + const matchingUserId = ppidSource && eids.find(userID => userID.source === ppidSource); if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') { const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, ''); if (ppidValue.length >= 32 && ppidValue.length <= 150) { @@ -642,18 +623,6 @@ export const requestBidsHook = timedAuctionHook('userId', function requestBidsHo ]).then(() => { // pass available user id data to bid adapters addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); - const ppid = getPPID(); - if (ppid) { - if (isGptPubadsDefined()) { - window.googletag.pubads().setPublisherProvidedId(ppid); - } else { - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(function() { - window.googletag.pubads().setPublisherProvidedId(ppid); - }); - } - } uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); @@ -853,6 +822,24 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } } } + updatePPID(submodule.idObj); +} + +function updatePPID(userIds = getUserIds()) { + if (userIds && ppidSource) { + const ppid = getPPID(createEidsArray(userIds)); + if (ppid) { + if (isGptPubadsDefined()) { + window.googletag.pubads().setPublisherProvidedId(ppid); + } else { + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(function() { + window.googletag.pubads().setPublisherProvidedId(ppid); + }); + } + } + } } function initSubmodules(dest, submodules, consentData, forceRefresh = false) { @@ -1002,12 +989,28 @@ function updateSubmodules() { if (!addedUserIdHook && submodules.length) { // priority value 40 will load after consentManagement with a priority of 50 getGlobal().requestBids.before(requestBidsHook, 40); + adapterManager.callDataDeletionRequest.before(requestDataDeletion); coreGetPPID.after((next) => next(getPPID())); logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); addedUserIdHook = true; } } +export function requestDataDeletion(next, ...args) { + logInfo('UserID: received data deletion request; deleting all stored IDs...') + submodules.forEach(submodule => { + if (typeof submodule.submodule.onDataDeletionRequest === 'function') { + try { + submodule.submodule.onDataDeletionRequest(submodule.config, submodule.idObj, ...args); + } catch (e) { + logError(`Error calling onDataDeletionRequest for ID submodule ${submodule.submodule.name}`, e); + } + } + deleteStoredValue(submodule); + }) + next.apply(this, args); +} + /** * enable submodule in User ID * @param {Submodule} submodule @@ -1075,3 +1078,11 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { init(config); module('userId', attachIdSystem); + +export function setOrtbUserExtEids(ortbRequest, bidderRequest, context) { + const eids = deepAccess(context, 'bidRequests.0.userIdAsEids'); + if (eids) { + deepSetValue(ortbRequest, 'user.ext.eids', eids); + } +} +registerOrtbProcessor({type: REQUEST, name: 'userExtEids', fn: setOrtbUserExtEids}); diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 195851ad23a..3f3bb66d8ae 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,7 +1,9 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; +import { config } from '../src/config.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; @@ -12,6 +14,7 @@ const TTL_SECONDS = 60 * 5; const DEAL_ID_EXPIRY = 1000 * 60 * 15; const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; const SESSION_ID_KEY = 'vidSid'; +const OPT_CACHE_KEY = 'vdzwopt'; export const SUPPORTED_ID_SYSTEMS = { 'britepoolid': 1, 'criteoId': 1, @@ -24,11 +27,12 @@ export const SUPPORTED_ID_SYSTEMS = { 'tdid': 1, 'pubProvidedId': 1 }; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); +const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); function getTopWindowQueryParams() { try { - const parsedUrl = parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -56,9 +60,23 @@ function isBidRequestValid(bid) { return !!(extractCID(params) && extractPID(params)); } -function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { - const { params, bidId, userId, adUnitCode, schain } = bid; - const { bidFloor, ext } = params; +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + auctionId, + transactionId, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; const hashUrl = hashCode(topWindowUrl); const dealId = getNextDealId(hashUrl); const uniqueDealId = getUniqueDealId(hashUrl); @@ -66,6 +84,24 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { const cId = extractCID(params); const pId = extractPID(params); const subDomain = extractSubDomain(params); + const ptrace = getCacheOpt(); + const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); + const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } let data = { url: encodeURIComponent(topWindowUrl), @@ -83,7 +119,21 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { bidderVersion: BIDDER_VERSION, prebidVersion: '$prebid.version$', res: `${screen.width}x${screen.height}`, - schain: schain + schain: schain, + mediaTypes: mediaTypes, + ptrace: ptrace, + isStorageAllowed: isStorageAllowed, + gpid: gpid, + cat: cat, + pagecat: pagecat, + auctionId: auctionId, + transactionId: transactionId, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout, + webSessionId: webSessionId }; appendUserIdsToRequestPayload(data, userId); @@ -100,6 +150,14 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest) { data.usPrivacy = bidderRequest.uspConsent; } + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + const dto = { method: 'POST', url: `${createDomain(subDomain)}/prebid/multi/${cId}`, @@ -142,10 +200,11 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { function buildRequests(validBidRequests, bidderRequest) { // TODO: does the fallback make sense here? const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); requests.push(request); }); return requests; @@ -155,18 +214,19 @@ function interpretResponse(serverResponse, request) { if (!serverResponse || !serverResponse.body) { return []; } - const { bidId } = request.data; - const { results } = serverResponse.body; + const {bidId} = request.data; + const {results} = serverResponse.body; let output = []; try { results.forEach(result => { - const { creativeId, ad, price, exp, width, height, currency, advertiserDomains } = result; + const {creativeId, ad, price, exp, width, height, currency, advertiserDomains, mediaType = BANNER} = result; if (!ad || !price) { return; } - output.push({ + + const response = { requestId: bidId, cpm: price, width: width, @@ -175,11 +235,22 @@ function interpretResponse(serverResponse, request) { currency: currency || CURRENCY, netRevenue: true, ttl: exp || TTL_SECONDS, - ad: ad, meta: { advertiserDomains: advertiserDomains || [] } - }) + }; + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); }); return output; } catch (e) { @@ -189,8 +260,8 @@ function interpretResponse(serverResponse, request) { function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { let syncs = []; - const { iframeEnabled, pixelEnabled } = syncOptions; - const { gdprApplies, consentString = '' } = gdprConsent; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` @@ -214,7 +285,9 @@ export function hashCode(s, prefix = '_') { let h = 0 let i = 0; if (l > 0) { - while (i < l) { h = (h << 5) - h + s.charCodeAt(i++) | 0; } + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } } return prefix + h; } @@ -258,10 +331,21 @@ export function getVidazooSessionId() { return getStorageItem(SESSION_ID_KEY) || ''; } +export function getCacheOpt() { + let data = storage.getDataFromLocalStorage(OPT_CACHE_KEY); + if (!data) { + data = String(Date.now()); + storage.setDataInLocalStorage(OPT_CACHE_KEY, data); + } + + return data; +} + export function getStorageItem(key) { try { return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { } + } catch (e) { + } return null; } @@ -269,9 +353,10 @@ export function getStorageItem(key) { export function setStorageItem(key, value, timestamp) { try { const created = timestamp || Date.now(); - const data = JSON.stringify({ value, created }); + const data = JSON.stringify({value, created}); storage.setDataInLocalStorage(key, data); - } catch (e) { } + } catch (e) { + } } export function tryParseJSON(value) { @@ -286,7 +371,7 @@ export const spec = { code: BIDDER_CODE, version: BIDDER_VERSION, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/videoModule/adQueue.js b/modules/videoModule/adQueue.js new file mode 100644 index 00000000000..a6da3752f6e --- /dev/null +++ b/modules/videoModule/adQueue.js @@ -0,0 +1,63 @@ +import { AD_BREAK_END, AUCTION_AD_LOAD_ATTEMPT, AUCTION_AD_LOAD_QUEUED, SETUP_COMPLETE } from '../../libraries/video/constants/events.js' +import { getExternalVideoEventName } from '../../libraries/video/shared/helpers.js' + +export function AdQueueCoordinator(videoCore, pbEvents) { + const storage = {}; + + function registerProvider(divId) { + storage[divId] = []; + videoCore.onEvents([SETUP_COMPLETE], onSetupComplete, divId); + } + + function queueAd(adTagUrl, divId, options) { + const queue = storage[divId]; + if (queue) { + queue.push({adTagUrl, options}); + triggerEvent(AUCTION_AD_LOAD_QUEUED, adTagUrl, options); + } else { + loadAd(divId, adTagUrl, options); + } + } + + return { + registerProvider, + queueAd + }; + + function onSetupComplete(eventName, eventPayload) { + const divId = eventPayload.divId; + videoCore.offEvents([SETUP_COMPLETE], onSetupComplete, divId); + loadQueuedAd(divId); + } + + function onAdBreakEnd(eventName, eventPayload) { + loadQueuedAd(eventPayload.divId); + } + + function loadQueuedAd(divId) { + videoCore.offEvents([AD_BREAK_END], onAdBreakEnd, divId); + const adQueue = storage[divId]; + if (!adQueue) { + return; + } + + if (!adQueue.length) { + delete storage[divId]; + return; + } + + const queuedAd = adQueue.shift(); + videoCore.onEvents([AD_BREAK_END], onAdBreakEnd, divId); + loadAd(divId, queuedAd.adTagUrl, queuedAd.options); + } + + function loadAd(divId, adTagUrl, options) { + triggerEvent(AUCTION_AD_LOAD_ATTEMPT, adTagUrl, options); + videoCore.setAdTagUrl(adTagUrl, divId, options); + } + + function triggerEvent(eventName, adTagUrl, options) { + const payload = Object.assign({ adTagUrl }, options); + pbEvents.emit(getExternalVideoEventName(eventName), payload); + } +} diff --git a/modules/videoModule/addingSubmodule.md b/modules/videoModule/addingSubmodule.md new file mode 100644 index 00000000000..2f880fdef3a --- /dev/null +++ b/modules/videoModule/addingSubmodule.md @@ -0,0 +1,528 @@ +# How to Add a Video Submodule + +Video submodules interact with the Video Module to integrate Prebid with Video Players, allowing Prebid to automatically: +- render bids in the desired video player +- mark used bids as won +- trigger player and media events +- populate the oRTB Video Impression and Content params in the bid request + +## Overview + +The Prebid Video Module simplifies the way Prebid integrates with video players by acting as a single point of contact for everything video. +In order for the Video Module to connect to a video player, a submodule must be implemented. The submodule acts as a bridge between the Video Module and the video player. +The Video Module will route commands and tasks to the appropriate submodule instance. +A submodule is expected to work for a specific video player. i.e. the JW Player submodule is used to integrate Prebid with JW Player. The video.js submdule connects to video.js. +Publishers who use players from different vendors on the same page can use multiple video submodules. + +## Requirements + +The Video Module only supports integration with Video Players that meet the following requirements: +- Must support parsing and reproduction of VAST ads + - Input can be an ad tag URL or the actual Vast XML. +- Must expose an API that allows the procurement of [Open RTB params](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf) for Video (section 3.2.7) and Content (section 3.2.16). +- Must emit javascript events for Ads and Media + - see [Event Registration](#event-registration) + +## Creating a Submodule + +### Step 1: Add a markdown file describing the submodule + +Create a markdown file under `modules` with the name of the module suffixed with 'VideoProvider', i.e. `exampleVideoProvider.md`. + +Example markdown file: +```markdown +# Overview + +Module Name: Example Video Provider +Module Type: Video Submodule +Video Player: Example player +Player website: example-player.com +Maintainer: someone@example.com + +# Description + +Video provider for Example Player. Contact someone@example.com for information. + +# Requirements + +Your page must link the Example Player build from our CDN. Alternatively yu can use npm to load the build. +``` + +### Step 2: Add a Vendor Code + +Vendor codes are required to indicate which submodule type to instantiate. Add your vendor code constant to an export const in `vendorCodes.js` in Prebid.js under `libraries/video/constants/vendorCodes.js`. +i.e. in `vendorCodes.js`: + +```javascript +export const EXAMPLE_PLAYER_VENDOR = 3; +``` + +### Step 2: Build the Module + +Now create a javascript file under `modules` with the name of the module suffixed with 'VideoProvider', e.g., `exampleVideoProvider.js`. + +#### The Submodule factory + +The Video Module will need a submodule instance for every player instance registered with Prebid. You will therefore need to implement a submodule factory which is called with a `videoProviderConfig` argument and returns a Video Provider instance. +Your submodule should import your vendor code constant and set it to a `vendorCode` property on your submodule factory. +Your submodule should also import the `submodule` function from `src/hook.js` and should use it to register as a submodule of `'video'`. + +**Code Example** + +```javascript +import { submodule } from '../src/hook.js'; + +function exampleSubmoduleFactory(videoProviderConfig) { + const videoProvider = { + // implementation + }; + + return videoProvider; +} + +exampleSubmoduleFactory.vendorCode = EXAMPLE_VENDOR; +submodule('video', exampleSubmoduleFactory); +``` + +#### The Submodule object + +The submodule object must adhere to the `VideoProvider` interface defined in the `coreVideo.js` inline documentation. + +#### Event registration + +Submodules must support attaching and detaching event listeners on the video player. The list of events are defined in the Events file in the Video Library: `libraries/video/constants/events.js`. +All events and their params must be supported. + +##### Event params + +All Video Module events include a `divId` and `type` param in the payload by default. +The `divId` is the div id string of the player emitting the event; it can be used as an identifier. The `type` is the string name of the event. +The remaining Payload params are listed in the following: + +###### SETUP_COMPLETE + +| argument name | type | description | +| ------------- | ---- | ----------- | +| playerVersion | string | The version of the player on the page | +| viewable | boolean | Is the player currently viewable? | +| viewabilityPercentage | number | The percentage of the video that is currently viewable on the user's screen. | +| mute | boolean | Whether or not the player is currently muted. | +| volumePercentage | number | The volume of the player, as a percentage | + +###### SETUP_FAILED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| playerVersion | string | The version of the player on the page | +| errorCode | number | The identifier of the error preventing the media from rendering | +| errorMessage | string | Developer friendly description of the reason the error occurred. | +| sourceError | object | The underlying root Error which prevented the playback. | + +###### DESTROYED +No additional params. + +###### AD_REQUEST + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | + +###### AD_BREAK_START + +| argument name | type | description | +| ------------- | ---- | ----------- | +| offset | string | Scheduled position in the video for the ad to play. For mid-rolls, will be the position in seconds as string. Other options: 'pre' (pre-roll), 'post' (post-roll), 'api' (ad was not scheduled) | + +###### AD_LOADED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| offset | string | Scheduled position in the video for the ad to play. For mid-rolls, will be the position in seconds as string. Other options: 'pre' (pre-roll), 'post' (post-roll), 'api' (ad was not scheduled) | +| loadTime | number | Time the ad took to load in milliseconds | +| vastAdId | string | The ID given to the ad within the ad tag's XML. Nullable when absent from the VAST xml. | +| adDescription | string | Description of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| adServer | string | Ad server used (e.g. dart or mediamind) from the vast tag. Nullable when absent from the VAST xml. | +| adTitle | string | Title of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| advertiserId | string | Optional identifier for the advertiser, provided by the ad server. Nullable when absent from the VAST xml. | +| advertiserName | string | Name of the advertiser as defined by the ad serving party, from the vast XML. Nullable when absent from the VAST xml. | +| dealId | string | The ID of the Ads deal. Generally relates to Direct Sold Ad Campaigns. Nullable when absent from the VAST xml. | +| linear | boolean | Is the ad linear or not? | +| vastVersion | string | Version of VAST being reported from the tag | +| creativeUrl | string | The URL representing the VPAID or MP4 ad that is run | +| adId | string | Unique Ad ID - refers to the 'attribute' of the node within the VAST. Nullable when absent from the VAST xml. | +| universalAdId | string | Unique identifier for an ad in VAST4. Nullable when absent from the VAST xml. | +| creativeId | string | Ad server's unique ID for the creative pulled from the ad tag's XML. Should be used to specify the ad server’s unique identifier as opposed to the Universal Ad Id which is used for maintaining a creative id for the ad across multiple systems. Nullable when absent from the VAST xml. | +| creativeType | string | The MIME type of the ad creative currently being displayed | +| redirectUrl | string | the url to which the viewer is being redirected after clicking the ad. Nullable when absent from the VAST xml. | +| adPlacementType | number | The video placements per IAB guidelines. Enum list: In-Stream: 1, In-Banner: 2, In-Article: 3, In-Feed: 4, Interstitial/Slider/Floating: 5 | +| waterfallIndex | number | Index of the current item in the ad waterfall | +| waterfallCount | number | The count of items in a given ad waterfall | +| adPodCount | number | the total number of ads in the pod | +| adPodIndex | number | The index of the currently playing ad within an ad pod | +| wrapperAdIds | array[string] | Ad IDs of the VAST Wrappers that were loaded while loading the Ad tag. The list returned starts at the inline ad (innermost) and traverses to the outermost wrapper ad. An empty array is returned if there are no wrapper ads. | + +###### AD_STARTED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| offset | string | Scheduled position in the video for the ad to play. For mid-rolls, will be the position in seconds as string. Other options: 'pre' (pre-roll), 'post' (post-roll), 'api' (ad was not scheduled) | +| loadTime | number | Time the ad took to load in milliseconds | +| vastAdId | string | The ID given to the ad within the ad tag's XML. Nullable when absent from the VAST xml. | +| adDescription | string | Description of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| adServer | string | Ad server used (e.g. dart or mediamind) from the vast tag. Nullable when absent from the VAST xml. | +| adTitle | string | Title of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| advertiserId | string | Optional identifier for the advertiser, provided by the ad server. Nullable when absent from the VAST xml. | +| advertiserName | string | Name of the advertiser as defined by the ad serving party, from the vast XML. Nullable when absent from the VAST xml. | +| dealId | string | The ID of the Ads deal. Generally relates to Direct Sold Ad Campaigns. Nullable when absent from the VAST xml. | +| linear | boolean | Is the ad linear or not? | +| vastVersion | string | Version of VAST being reported from the tag | +| creativeUrl | string | The URL representing the VPAID or MP4 ad that is run | +| adId | string | Unique Ad ID - refers to the 'attribute' of the node within the VAST. Nullable when absent from the VAST xml. | +| universalAdId | string | Unique identifier for an ad in VAST4. Nullable when absent from the VAST xml. | +| creativeId | string | Ad server's unique ID for the creative pulled from the ad tag's XML. Should be used to specify the ad server’s unique identifier as opposed to the Universal Ad Id which is used for maintaining a creative id for the ad across multiple systems. Nullable when absent from the VAST xml. | +| creativeType | string | The MIME type of the ad creative currently being displayed | +| redirectUrl | string | the url to which the viewer is being redirected after clicking the ad. Nullable when absent from the VAST xml. | +| adPlacementType | number | The video placements per IAB guidelines. Enum list: In-Stream: 1, In-Banner: 2, In-Article: 3, In-Feed: 4, Interstitial/Slider/Floating: 5 | +| waterfallIndex | number | Index of the current item in the ad waterfall | +| waterfallCount | number | The count of items in a given ad waterfall | +| adPodCount | number | the total number of ads in the pod | +| adPodIndex | number | The index of the currently playing ad within an ad pod | +| wrapperAdIds | array[string] | Ad IDs of the VAST Wrappers that were loaded while loading the Ad tag. The list returned starts at the inline ad (innermost) and traverses to the outermost wrapper ad. An empty array is returned if there are no wrapper ads. | + +
+ +###### AD_IMPRESSION + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| offset | string | Scheduled position in the video for the ad to play. For mid-rolls, will be the position in seconds as string. Other options: 'pre' (pre-roll), 'post' (post-roll), 'api' (ad was not scheduled) | +| loadTime | number | Time the ad took to load in milliseconds | +| vastAdId | string | The ID given to the ad within the ad tag's XML. Nullable when absent from the VAST xml. | +| adDescription | string | Description of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| adServer | string | Ad server used (e.g. dart or mediamind) from the vast tag. Nullable when absent from the VAST xml. | +| adTitle | string | Title of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| advertiserId | string | Optional identifier for the advertiser, provided by the ad server. Nullable when absent from the VAST xml. | +| advertiserName | string | Name of the advertiser as defined by the ad serving party, from the vast XML. Nullable when absent from the VAST xml. | +| dealId | string | The ID of the Ads deal. Generally relates to Direct Sold Ad Campaigns. Nullable when absent from the VAST xml. | +| linear | boolean | Is the ad linear or not? | +| vastVersion | string | Version of VAST being reported from the tag | +| creativeUrl | string | The URL representing the VPAID or MP4 ad that is run | +| adId | string | Unique Ad ID - refers to the 'attribute' of the node within the VAST. Nullable when absent from the VAST xml. | +| universalAdId | string | Unique identifier for an ad in VAST4. Nullable when absent from the VAST xml. | +| creativeId | string | Ad server's unique ID for the creative pulled from the ad tag's XML. Should be used to specify the ad server’s unique identifier as opposed to the Universal Ad Id which is used for maintaining a creative id for the ad across multiple systems. Nullable when absent from the VAST xml. | +| creativeType | string | The MIME type of the ad creative currently being displayed | +| redirectUrl | string | the url to which the viewer is being redirected after clicking the ad. Nullable when absent from the VAST xml. | +| adPlacementType | number | The video placements per IAB guidelines. Enum list: In-Stream: 1, In-Banner: 2, In-Article: 3, In-Feed: 4, Interstitial/Slider/Floating: 5 | +| waterfallIndex | number | Index of the current item in the ad waterfall | +| waterfallCount | number | The count of items in a given ad waterfall | +| adPodCount | number | the total number of ads in the pod | +| adPodIndex | number | The index of the currently playing ad within an ad pod | +| wrapperAdIds | array[string] | Ad IDs of the VAST Wrappers that were loaded while loading the Ad tag. The list returned starts at the inline ad (innermost) and traverses to the outermost wrapper ad. An empty array is returned if there are no wrapper ads. | +| time | number | The playback time in the ad when the event occurs, in seconds. | +| duration | number | Total duration of an ad in seconds | + +###### AD_PLAY + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | + +###### AD_TIME + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| time | number | The current poisition in the ad timeline | +| duration | number | Total duration of an ad in seconds | + +###### AD_PAUSE + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | + +###### AD_CLICK + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| offset | string | Scheduled position in the video for the ad to play. For mid-rolls, will be the position in seconds as string. Other options: 'pre' (pre-roll), 'post' (post-roll), 'api' (ad was not scheduled) | +| loadTime | number | Time the ad took to load in milliseconds | +| vastAdId | string | The ID given to the ad within the ad tag's XML. Nullable when absent from the VAST xml. | +| adDescription | string | Description of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| adServer | string | Ad server used (e.g. dart or mediamind) from the vast tag. Nullable when absent from the VAST xml. | +| adTitle | string | Title of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| advertiserId | string | Optional identifier for the advertiser, provided by the ad server. Nullable when absent from the VAST xml. | +| advertiserName | string | Name of the advertiser as defined by the ad serving party, from the vast XML. Nullable when absent from the VAST xml. | +| dealId | string | The ID of the Ads deal. Generally relates to Direct Sold Ad Campaigns. Nullable when absent from the VAST xml. | +| linear | boolean | Is the ad linear or not? | +| vastVersion | string | Version of VAST being reported from the tag | +| creativeUrl | string | The URL representing the VPAID or MP4 ad that is run | +| adId | string | Unique Ad ID - refers to the 'attribute' of the node within the VAST. Nullable when absent from the VAST xml. | +| universalAdId | string | Unique identifier for an ad in VAST4. Nullable when absent from the VAST xml. | +| creativeId | string | Ad server's unique ID for the creative pulled from the ad tag's XML. Should be used to specify the ad server’s unique identifier as opposed to the Universal Ad Id which is used for maintaining a creative id for the ad across multiple systems. Nullable when absent from the VAST xml. | +| creativeType | string | The MIME type of the ad creative currently being displayed | +| redirectUrl | string | the url to which the viewer is being redirected after clicking the ad. Nullable when absent from the VAST xml. | +| adPlacementType | number | The video placements per IAB guidelines. Enum list: In-Stream: 1, In-Banner: 2, In-Article: 3, In-Feed: 4, Interstitial/Slider/Floating: 5 | +| waterfallIndex | number | Index of the current item in the ad waterfall | +| waterfallCount | number | The count of items in a given ad waterfall | +| adPodCount | number | the total number of ads in the pod | +| adPodIndex | number | The index of the currently playing ad within an ad pod | +| wrapperAdIds | array[string] | Ad IDs of the VAST Wrappers that were loaded while loading the Ad tag. The list returned starts at the inline ad (innermost) and traverses to the outermost wrapper ad. An empty array is returned if there are no wrapper ads. | +| time | number | The playback time in the ad when the event occurs, in seconds. | +| duration | number | Total duration of an ad in seconds | + +###### AD_SKIPPED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| time | number | The playback time in the ad when the event occurs, in seconds. | +| duration | number | Total duration of an ad in seconds | + + + +###### AD_ERROR + +| argument name | type | description | +| ------------- | ---- | ----------- | +| playerErrorCode | number | The ad error code from the Player’s internal spec. | +| vastErrorCode | number | The error code for the VAST response that is returned from the request, as defined in the VAST spec. | +| errorMessage | string | Developer friendly description of the reason the error occurred. | +| sourceError | object | The underlying root Error which prevented the playback. | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| offset | string | Scheduled position in the video for the ad to play. For mid-rolls, will be the position in seconds as string. Other options: 'pre' (pre-roll), 'post' (post-roll), 'api' (ad was not scheduled) | +| loadTime | number | Time the ad took to load in milliseconds | +| vastAdId | string | The ID given to the ad within the ad tag's XML. Nullable when absent from the VAST xml. | +| adDescription | string | Description of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| adServer | string | Ad server used (e.g. dart or mediamind) from the vast tag. Nullable when absent from the VAST xml. | +| adTitle | string | Title of the ad pulled from the ad tag's XML. Nullable when absent from the VAST xml. | +| advertiserId | string | Optional identifier for the advertiser, provided by the ad server. Nullable when absent from the VAST xml. | +| advertiserName | string | Name of the advertiser as defined by the ad serving party, from the vast XML. Nullable when absent from the VAST xml. | +| dealId | string | The ID of the Ads deal. Generally relates to Direct Sold Ad Campaigns. Nullable when absent from the VAST xml. | +| linear | boolean | Is the ad linear or not? | +| vastVersion | string | Version of VAST being reported from the tag | +| creativeUrl | string | The URL representing the VPAID or MP4 ad that is run | +| adId | string | Unique Ad ID - refers to the 'attribute' of the node within the VAST. Nullable when absent from the VAST xml. | +| universalAdId | string | Unique identifier for an ad in VAST4. Nullable when absent from the VAST xml. | +| creativeId | string | Ad server's unique ID for the creative pulled from the ad tag's XML. Should be used to specify the ad server’s unique identifier as opposed to the Universal Ad Id which is used for maintaining a creative id for the ad across multiple systems. Nullable when absent from the VAST xml. | +| creativeType | string | The MIME type of the ad creative currently being displayed | +| redirectUrl | string | the url to which the viewer is being redirected after clicking the ad. Nullable when absent from the VAST xml. | +| adPlacementType | number | The video placements per IAB guidelines. Enum list: In-Stream: 1, In-Banner: 2, In-Article: 3, In-Feed: 4, Interstitial/Slider/Floating: 5 | +| waterfallIndex | number | Index of the current item in the ad waterfall | +| waterfallCount | number | The count of items in a given ad waterfall | +| adPodCount | number | the total number of ads in the pod | +| adPodIndex | number | The index of the currently playing ad within an ad pod | +| wrapperAdIds | array[string] | Ad IDs of the VAST Wrappers that were loaded while loading the Ad tag. The list returned starts at the inline ad (innermost) and traverses to the outermost wrapper ad. An empty array is returned if there are no wrapper ads. | +| time | number | The playback time in the ad when the event occurs, in seconds. | +| duration | number | Total duration of an ad in seconds | + +###### AD_COMPLETE + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | + +###### AD_BREAK_END + +| argument name | type | description | +| ------------- | ---- | ----------- | +| offset | string | Scheduled position in the video for the ad to play. For mid-rolls, will be the position in seconds as string. Other options: 'pre' (pre-roll), 'post' (post-roll), 'api' (ad was not scheduled) | + +###### PLAYLIST + +| argument name | type | description | +| ------------- | ---- | ----------- | +| playlistItemCount | number | The number of items in the current playlist | +| autostart | boolean | Whether or not the player is set to begin playing automatically. | + +###### PLAYBACK_REQUEST + +| argument name | type | description | +| ------------- | ---- | ----------- | +| playReason | string | wWy the play attempt originated. Options: ‘Unknown’ (Unknown reason:we cannot tell), ‘Interaction’ (A viewer interacts with the UI), ‘Auto’ (Autoplay based on the configuration of the player - autoStart), ‘autoOnViewable’ (autoStart when viewable), ‘autoRepeat’ (media automatically restarted after completion, without any user interaction), ‘Api’ (caused by a call on the player’s API), ‘Internal’ (started because of an internal mechanism i.e. playlist progressed to a recommended item) | + +###### AUTOSTART_BLOCKED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| errorCode | number | The identifier of error preventing the media from rendering | +| errorMessage | string | Developer friendly description of the reason the error occurred. | +| sourceError | object | The underlying root Error which prevented the playback. | + +###### PLAY_ATTEMPT_FAILED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| playReason | string | Why the play attempt originated. Options: ‘Unknown’ (Unknown reason:we cannot tell), ‘Interaction’ (A viewer interacts with the UI), ‘Auto’ (Autoplay based on the configuration of the player - autoStart), ‘autoOnViewable’ (autoStart when viewable), ‘autoRepeat’ (media automatically restarted after completion, without any user interaction), ‘Api’ (caused by a call on the player’s API), ‘Internal’ (started because of an internal mechanism i.e. playlist progressed to a recommended item) | +| errorCode | number | The identifier of error preventing the media from rendering | +| errorMessage | string | Developer friendly description of the reason the error occurred. | +| sourceError | object | The underlying root Error which prevented the playback. | + +###### CONTENT_LOADED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| contentId | string | The unique identifier of the media item being rendered by the video player. Nullable when not provided by Publisher, or unknown. | +| contentUrl | string | The URL of the media source of the playlist item | +| title | string | The title of the content; not meant to be used as a unique identifier. Nullable when not provided by Publisher, or unknown. | +| description | string | The description of the content. Nullable when not provided by Publisher, or unknown. | +| playlistIndex | number | The currently playing media item's index in the playlist. | +| contentTags | array[string] | Customer media level tags describing the content. Nullable when not provided by Publisher, or unknown. | + +###### PLAY + +No additional params. + +###### PAUSE + +No additional params. + +###### BUFFER + +| argument name | type | description | +| ------------- | ---- | ----------- | +| time | number | Playback position of the media in seconds | +| duration | number | Current media’s length in seconds | +| playbackMode | number | The current playback mode used by a given player. Enum list: vod: 0, live: 1, dvr: 2 | + +###### TIME + +| argument name | type | description | +| ------------- | ---- | ----------- | +| position | number | Playback position of the media in seconds | +| duration | number | Current media’s length in seconds | + +###### SEEK_START + +| argument name | type | description | +| ------------- | ---- | ----------- | +| position | number | Playback position of the media in seconds, when the seek begins | +| destination | number | Desired playback position of a seek action, in seconds | +| duration | number | Current media’s length in seconds | + +###### SEEK_END + +| argument name | type | description | +| ------------- | ---- | ----------- | +| position | number | Playback position of the media in seconds, when the seek has ended | +| duration | number | Current media’s length in seconds | + +###### MUTE + +| argument name | type | description | +| ------------- | ---- | ----------- | +| mute | boolean | Whether or not the player is currently muted. | + +###### VOLUME + +| argument name | type | description | +| ------------- | ---- | ----------- | +| volumePercentage | number | The volume of the player, as a percentage | + +###### RENDITION_UPDATE + +| argument name | type | description | +| ------------- | ---- | ----------- | +| videoReportedBitrate | number | The bitrate of the currently playing video in kbps as reported by the Adaptive Manifest. | +| audioReportedBitrate | number | The bitrate of the currently playing audio in kbps as reported by the Adaptive Manifest. | +| encodedVideoWidth | number | The encoded width in pixels of the currently playing video rendition. | +| encodedVideoHeight | number | The encoded height in pixels of the currently playing video rendition. | +| videoFramerate | number | The current rate of playback. For a video that is playing twice as fast as the default playback, the playbackRate value should be 2.00 | + +###### ERROR + +| argument name | type | description | +| ------------- | ---- | ----------- | +| errorCode | number | The identifier of the error preventing the media from rendering | +| errorMessage | string | Developer friendly description of the reason the error occurred. | +| sourceError | object | The underlying root Error which prevented the playback. | + +###### COMPLETE + +No additional params. + +###### PLAYLIST_COMPLETE + +No additional params. + +###### FULLSCREEN + +| argument name | type | description | +| ------------- | ---- | ----------- | +| fullscreen | boolean | Whether or not the player is currently in fullscreen | + +###### PLAYER_RESIZE + +| argument name | type | description | +| ------------- | ---- | ----------- | +| height | number | The height of the player in pixels | +| width | number | The width of the player in pixels | + +###### VIEWABLE + +| argument name | type | description | +| ------------- | ---- | ----------- | +| viewable | boolean | Is the player currently viewable? | +| viewabilityPercentage | number | The percentage of the video that is currently viewable on the user's screen. | + +###### CAST + +| argument name | type | description | +| ------------- | ---- | ----------- | +| casting | boolean | Whether or not the current user is casting to a device | + +###### AUCTION_AD_LOAD_ATTEMPT + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| adUnitCode | string | Unique identifier that was used when creating the ad unit. | + +###### AUCTION_AD_LOAD_QUEUED + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adTagUrl | string | The URL for the ad tag associated with the given ad event | +| adUnitCode | string | Unique identifier that was used when creating the ad unit. | + +###### AUCTION_AD_LOAD_ABORT + +| argument name | type | description | +| ------------- | ---- | ----------- | +| adUnitCode | string | Unique identifier that was used when creating the ad unit. | + +###### BID_IMPRESSION + +| argument name | type | description | +| ------------- | ---- | ----------- | +| bid | object | Information about the Bid which resulted in the Ad Impression | +| adEvent | object | Event payload from the [Ad Impression](#ad-impression-params) | + +###### BID_ERROR + +| argument name | type | description | +| ------------- | ---- | ----------- | +| bid | object | Information about the Bid which resulted in the Ad Error | +| adEvent | object | Event payload from the [Ad Error](#ad-error-params) | + +#### Update .submodules.json + +In prebid.js, add your new submodule to `.submodules.json` under the `videoModule` as such: +{% highlight text %} +``` +{ + "parentModules": { + "videoModule": [ + "exampleVideoProvider" + ] + } +} +``` + +### Shared resources for developers + +A video library containing reusable code and constants has been added to Prebid.js for your convenience. We encourage you to import from this library. +Constants such as event names can be found in the `libraries/video/constants/` folder. diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js new file mode 100644 index 00000000000..ce66acc2b02 --- /dev/null +++ b/modules/videoModule/coreVideo.js @@ -0,0 +1,240 @@ +import { module } from '../../src/hook.js'; +import { ParentModule, SubmoduleBuilder } from '../../libraries/video/shared/parentModule.js'; + +// define, ortb object, events + +/** + * Video Provider Submodule interface. All submodules of the Core Video module must adhere to this. + * @description attached to a video player instance. + * @typedef {Object} VideoProvider + * @function init - Instantiates the Video Provider and the video player, if not already instantiated. + * @function getId - retrieves the div id (unique identifier) of the attached player instance. + * @function getOrtbVideo - retrieves the oRTB Video params for a player's current video session. + * @function getOrtbContent - retrieves the oRTB Content params for a player's current video session. + * @function setAdTagUrl - Requests that a player render the ad in the provided ad tag url. + * @function onEvent - attaches an event listener to the player instance. + * @function offEvent - removes event listener to the player instance. + * @function destroy - deallocates the player instance + */ + +/** + * @function VideoProvider#init + */ + +/** + * @function VideoProvider#getId + * @returns {string} + */ + +/** + * @function VideoProvider#getOrtbVideo + * @returns {Object} + */ + +/** + * @function VideoProvider#getOrtbContent + * @returns {Object} + */ + +/** + * @function VideoProvider#setAdTagUrl + * @param {string} adTagUrl - URL to a VAST ad tag + * @param {Object} options - Optional params + */ + +/** + * @function VideoProvider#onEvent + * @param {string} event - name of event for which the listener should be added + * @param {function} callback - function that will get called when the event is triggered + * @param {Object} basePayload - Base payload for every event; includes common parameters such as divId and type. Event payload should be built on top of this. + */ + +/** + * @function VideoProvider#offEvent + * @param {string} event - name of event for which the attached listener should be removed + * @param {function} callback - function that was assigned as a callback when the listener was added + */ + +/** + * @function VideoProvider#destroy + */ + +/** + * @typedef {Object} videoProviderConfig + * @name videoProviderConfig + * @summary contains data indicating which submodule to create and which player instance to attach it to + * @property {string} divId - unique identifier of the player instance + * @property {number} vendorCode - numeric identifier of the Video Provider type i.e. video.js or jwplayer + * @property {playerConfig} playerConfig + */ + +/** + * @typedef {Object} playerConfig + * @name playerConfig + * @summary contains data indicating the behavior the player instance should have + * @property {boolean} autoStart - determines if the player should start automatically when instantiated + * @property {boolean} mute - determines if the player should be muted when instantiated + * @property {string} licenseKey - authentication key required for commercial players. Optional for free players. + * @property {playerVendorParams} params + */ + +/** + * @typedef playerVendorParams + * @name playerVendorParams + * @summary configuration options specific to a Video Vendor's Provider + * @property {Object} vendorConfig - the settings object which can be used as an argument when instantiating a player. Specific to the video player's API. + */ + +/** + * @typedef videoEvent + * + */ + +/** + * Routes commands to the appropriate video submodule. + * @typedef {Object} VideoCore + * @class + * @function registerProvider + * @function getOrtbVideo + * @function getOrtbContent + * @function setAdTagUrl + * @function onEvents + * @function offEvents + */ + +/** + * @summary Maps a Video Provider factory to the video player's vendor code. + * @type {vendorSubmoduleDirectory} + */ +const videoVendorDirectory = {}; + +/** + * @constructor + * @param {ParentModule} parentModule_ + * @returns {VideoCore} + */ +export function VideoCore(parentModule_) { + const parentModule = parentModule_; + + /** + * requests that a submodule be instantiated for the specific player instance described by the @providerConfig + * @name VideoCore#registerProvider + * @param {videoProviderConfig} providerConfig + */ + function registerProvider(providerConfig) { + try { + parentModule.registerSubmodule(providerConfig.divId, providerConfig.vendorCode, providerConfig); + } catch (e) {} + } + + function initProvider(divId) { + const submodule = parentModule.getSubmodule(divId); + submodule && submodule.init && submodule.init(); + } + + /** + * @name VideoCore#getOrtbVideo + * @summary Obtains the oRTB Video params for a player's current video session. + * @param {string} divId - unique identifier of the player instance + * @returns {Object} oRTB Video params + */ + function getOrtbVideo(divId) { + const submodule = parentModule.getSubmodule(divId); + return submodule && submodule.getOrtbVideo(); + } + + /** + * @name VideoCore#getOrtbContent + * @summary Obtains the oRTB Content params for a player's current video session. + * @param {string} divId - unique identifier of the player instance + * @returns {Object} oRTB Content params + */ + function getOrtbContent(divId) { + const submodule = parentModule.getSubmodule(divId); + return submodule && submodule.getOrtbContent(); + } + + /** + * @name VideoCore#setAdTagUrl + * @summary Requests that a player render the ad in the provided ad tag + * @param {string} adTagUrl - URL to a VAST ad tag + * @param {string} divId - unique identifier of the player instance + * @param {Object} options - additional params + */ + function setAdTagUrl(adTagUrl, divId, options) { + const submodule = parentModule.getSubmodule(divId); + submodule && submodule.setAdTagUrl(adTagUrl, options); + } + + /** + * @name VideoCore#onEvents + * @summary attaches event listeners + * @param {[string]} events - List of event names for which the listener should be added + * @param {function} callback - function that will get called when one of the events is triggered + * @param {string} divId - unique identifier of the player instance + */ + function onEvents(events, callback, divId) { + if (!callback) { + return; + } + + const submodule = parentModule.getSubmodule(divId); + if (!submodule) { + return; + } + + for (let i = 0; i < events.length; i++) { + const type = events[i]; + const basePayload = { + divId, + type + }; + submodule.onEvent(type, callback, basePayload); + } + } + + /** + * @name VideoCore#offEvents + * @summary removes event listeners + * @param {[string]} events - List of event names for which the listener should be removed + * @param {function} callback - function that was assigned as a callback when the listener was added + * @param {string} divId - unique identifier of the player instance + */ + function offEvents(events, callback, divId) { + const submodule = parentModule.getSubmodule(divId); + if (!submodule) { + return; + } + + events.forEach(event => { + submodule.offEvent(event, callback); + }); + } + + return { + registerProvider, + initProvider, + getOrtbVideo, + getOrtbContent, + setAdTagUrl, + onEvents, + offEvents + }; +} + +/** + * @function videoCoreFactory + * @summary Factory to create a Video Core instance + * @returns {VideoCore} + */ +export function videoCoreFactory() { + const videoSubmoduleBuilder = SubmoduleBuilder(videoVendorDirectory); + const parentModule = ParentModule(videoSubmoduleBuilder); + return VideoCore(parentModule); +} + +function attachVideoProvider(submoduleFactory) { + videoVendorDirectory[submoduleFactory.vendorCode] = submoduleFactory; +} + +module('video', attachVideoProvider); diff --git a/modules/videoModule/gamAdServerSubmodule.js b/modules/videoModule/gamAdServerSubmodule.js new file mode 100644 index 00000000000..87db71ae38b --- /dev/null +++ b/modules/videoModule/gamAdServerSubmodule.js @@ -0,0 +1,27 @@ +import { GAM_VENDOR } from '../../libraries/video/constants/vendorCodes.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; + +/** + * @constructor + * @param {Object} dfpModule_ - the DFP ad server module + * @returns {AdServerProvider} + */ +function GamAdServerProvider(dfpModule_) { + const dfp = dfpModule_; + + function getAdTagUrl(adUnit, baseAdTag, params) { + return dfp.buildVideoUrl({ adUnit: adUnit, url: baseAdTag, params }); + } + + return { + getAdTagUrl + } +} + +export function gamSubmoduleFactory() { + const dfp = getGlobal().adServers.dfp; + const gamProvider = GamAdServerProvider(dfp); + return gamProvider; +} + +gamSubmoduleFactory.vendorCode = GAM_VENDOR; diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js new file mode 100644 index 00000000000..fb3c621918d --- /dev/null +++ b/modules/videoModule/index.js @@ -0,0 +1,251 @@ +import { config } from '../../src/config.js'; +import { find } from '../../src/polyfill.js'; +import * as events from '../../src/events.js'; +import { mergeDeep } from '../../src/utils.js'; +import { getGlobal } from '../../src/prebidGlobal.js'; +import CONSTANTS from '../../src/constants.json'; +import { + videoEvents, + AUCTION_AD_LOAD_ATTEMPT, + AD_IMPRESSION, + AD_ERROR, + BID_IMPRESSION, + BID_ERROR, + AUCTION_AD_LOAD_ABORT, + AUCTION_AD_LOAD_QUEUED +} from '../../libraries/video/constants/events.js' +import { PLACEMENT } from '../../libraries/video/constants/ortb.js'; +import { videoKey } from '../../libraries/video/constants/constants.js' +import { videoCoreFactory } from './coreVideo.js'; +import { gamSubmoduleFactory } from './gamAdServerSubmodule.js'; +import { videoImpressionVerifierFactory } from './videoImpressionVerifier.js'; +import { AdQueueCoordinator } from './adQueue.js'; +import { getExternalVideoEventName } from '../../libraries/video/shared/helpers.js' + +const allVideoEvents = Object.keys(videoEvents).map(eventKey => videoEvents[eventKey]); +events.addEvents(allVideoEvents.concat([AUCTION_AD_LOAD_ATTEMPT, AUCTION_AD_LOAD_QUEUED, AUCTION_AD_LOAD_ABORT, BID_IMPRESSION, BID_ERROR]).map(getExternalVideoEventName)); + +/** + * This module adds User Video support to prebid.js + * @module modules/videoModule + */ +export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvents_, gamAdServerFactory_, videoImpressionVerifierFactory_, adQueueCoordinator_) { + const videoCore = videoCore_; + const getConfig = getConfig_; + const pbGlobal = pbGlobal_; + const requestBids = pbGlobal.requestBids; + const pbEvents = pbEvents_; + const videoEvents = videoEvents_; + const gamAdServerFactory = gamAdServerFactory_; + const adQueueCoordinator = adQueueCoordinator_; + let gamSubmodule; + let mainContentDivId; + let contentEnrichmentEnabled = true; + const videoImpressionVerifierFactory = videoImpressionVerifierFactory_; + let videoImpressionVerifier; + + function init() { + const cache = getConfig('cache'); + videoImpressionVerifier = videoImpressionVerifierFactory(!!cache); + getConfig(videoKey, ({ video }) => { + video.providers.forEach(provider => { + const divId = provider.divId; + videoCore.registerProvider(provider); + adQueueCoordinator.registerProvider(divId); + videoCore.initProvider(divId); + videoCore.onEvents(videoEvents, (type, payload) => { + pbEvents.emit(getExternalVideoEventName(type), payload); + }, divId); + + const adServerConfig = provider.adServer; + if (!gamSubmodule && adServerConfig) { + gamSubmodule = gamAdServerFactory(); + } + }); + contentEnrichmentEnabled = video.contentEnrichmentEnabled !== false; + mainContentDivId = contentEnrichmentEnabled ? video.mainContentDivId : null; + }); + + requestBids.before(beforeBidsRequested, 40); + + pbEvents.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { + videoImpressionVerifier.trackBid(bid); + }); + + pbEvents.on(getExternalVideoEventName(AD_IMPRESSION), function (payload) { + triggerVideoBidEvent(BID_IMPRESSION, payload); + }); + + pbEvents.on(getExternalVideoEventName(AD_ERROR), function (payload) { + triggerVideoBidEvent(BID_ERROR, payload); + }); + } + + function renderBid(divId, bid, options = {}) { + const adUrl = bid.vastUrl; + options.adXml = bid.vastXml; + options.winner = bid.bidder; + loadAdTag(adUrl, divId, options); + } + + function getOrtbVideo(divId) { + return videoCore.getOrtbVideo(divId); + } + + function getOrtbContent(divId) { + return videoCore.getOrtbContent(divId); + } + + return { init, renderBid, getOrtbVideo, getOrtbContent }; + + function beforeBidsRequested(nextFn, bidderRequest) { + enrichAuction(bidderRequest); + + const bidsBackHandler = bidderRequest.bidsBackHandler; + if (!bidsBackHandler || typeof bidsBackHandler !== 'function') { + pbEvents.on(CONSTANTS.EVENTS.AUCTION_END, auctionEnd); + } + + return nextFn.call(this, bidderRequest); + } + + function enrichAuction(bidderRequest) { + if (mainContentDivId) { + enrichOrtb2(mainContentDivId, bidderRequest); + } + + const adUnits = bidderRequest.adUnits || pbGlobal.adUnits || []; + adUnits.forEach(adUnit => { + const divId = getDivId(adUnit); + enrichAdUnit(adUnit, divId); + if (contentEnrichmentEnabled && !mainContentDivId) { + enrichOrtb2(divId, bidderRequest); + } + }); + } + + function getDivId(adUnit) { + const videoConfig = adUnit.video; + if (!adUnit.mediaTypes.video || !videoConfig) { + return; + } + + return videoConfig.divId; + } + + function enrichAdUnit(adUnit, videoDivId) { + const ortbVideo = getOrtbVideo(videoDivId); + if (!ortbVideo) { + return; + } + + const video = Object.assign({}, adUnit.mediaTypes.video, ortbVideo); + + video.context = ortbVideo.placement === PLACEMENT.INSTREAM ? 'instream' : 'outstream'; + + const width = ortbVideo.w; + const height = ortbVideo.h; + if (width && height) { + video.playerSize = [width, height]; + } + + adUnit.mediaTypes.video = video; + } + + function enrichOrtb2(divId, bidderRequest) { + const ortbContent = getOrtbContent(divId); + if (!ortbContent) { + return; + } + bidderRequest.ortb2 = mergeDeep({}, bidderRequest.ortb2, { site: { content: ortbContent } }); + } + + function auctionEnd(auctionResult) { + auctionResult.adUnits.forEach(adUnit => { + if (adUnit.video) { + renderWinningBid(adUnit); + } + }); + pbEvents.off(CONSTANTS.EVENTS.AUCTION_END, auctionEnd); + } + + function getAdServerConfig(adUnitVideoConfig) { + const globalVideoConfig = getConfig(videoKey); + const globalProviderConfig = globalVideoConfig.providers.find(provider => provider.divId === adUnitVideoConfig.divId) || {}; + if (!globalVideoConfig.adServer && !globalProviderConfig.adServer && !adUnitVideoConfig.adServer) { + return; + } + return mergeDeep({}, globalVideoConfig.adServer, globalProviderConfig.adServer, adUnitVideoConfig.adServer); + } + + function renderWinningBid(adUnit) { + const adUnitCode = adUnit.code; + const options = { adUnitCode }; + + const videoConfig = adUnit.video; + const divId = videoConfig.divId; + const adServerConfig = getAdServerConfig(videoConfig); + let adUrl; + if (adServerConfig) { + adUrl = gamSubmodule.getAdTagUrl(adUnit, adServerConfig.baseAdTagUrl, adServerConfig.params); + } + + if (adUrl) { + loadAdTag(adUrl, divId, options); + return; + } + + const highestCpmBids = pbGlobal.getHighestCpmBids(adUnitCode); + if (!highestCpmBids.length) { + pbEvents.emit(getExternalVideoEventName(AUCTION_AD_LOAD_ABORT), options); + return; + } + + const highestBid = highestCpmBids.shift(); + if (!highestBid) { + return; + } + + renderBid(divId, highestBid, options); + } + + // options: adXml, winner, adUnitCode, + function loadAdTag(adTagUrl, divId, options) { + adQueueCoordinator.queueAd(adTagUrl, divId, options); + } + + function triggerVideoBidEvent(eventName, adEventPayload) { + const bid = getBid(adEventPayload); + if (!bid) { + return; + } + + pbGlobal.markWinningBidAsUsed(bid); + pbEvents.emit(getExternalVideoEventName(eventName), { bid, adEvent: adEventPayload }); + } + + function getBid(adPayload) { + const { adId, adTagUrl, wrapperAdIds } = adPayload; + const bidIdentifiers = videoImpressionVerifier.getBidIdentifiers(adId, adTagUrl, wrapperAdIds); + if (!bidIdentifiers) { + return; + } + + const { adUnitCode, requestId, auctionId } = bidIdentifiers; + const bidAdId = bidIdentifiers.adId; + const { bids } = pbGlobal.getBidResponsesForAdUnitCode(adUnitCode); + return find(bids, bid => bid.adId === bidAdId && bid.requestId === requestId && bid.auctionId === auctionId); + } +} + +export function pbVideoFactory() { + const videoCore = videoCoreFactory(); + const adQueueCoordinator = AdQueueCoordinator(videoCore, events); + const pbGlobal = getGlobal(); + const pbVideo = PbVideo(videoCore, config.getConfig, pbGlobal, events, allVideoEvents, gamSubmoduleFactory, videoImpressionVerifierFactory, adQueueCoordinator); + pbVideo.init(); + pbGlobal.videoModule = pbVideo; + return pbVideo; +} + +pbVideoFactory(); diff --git a/modules/videoModule/videoImpressionVerifier.js b/modules/videoModule/videoImpressionVerifier.js new file mode 100644 index 00000000000..60717c0f855 --- /dev/null +++ b/modules/videoModule/videoImpressionVerifier.js @@ -0,0 +1,206 @@ +import { find } from '../../src/polyfill.js'; +import { vastXmlEditorFactory } from '../../libraries/video/shared/vastXmlEditor.js'; +import { generateUUID } from '../../src/utils.js'; + +export const PB_PREFIX = 'pb_'; +export const UUID_MARKER = PB_PREFIX + 'uuid'; + +/** + * Video Impression Verifier interface. All implementations of a Video Impression Verifier must comply with this interface. + * @description adds tracking markers to an ad and extracts the bid identifiers from ad event information. + * @typedef {Object} VideoImpressionVerifier + * @function trackBid - requests that a bid's ad be tracked for impression verification. + * @function getBidIdentifiers - requests information from the ad event data that can be used to match the ad to a tracked bid. + */ + +/** + * @function VideoImpressionVerifier#trackBid + * @param {Object} bid - Bid that should be tracked. + * @return {String} - Identifier for the bid being tracked. + */ + +/** + * @function VideoImpressionVerifier#getBidIdentifiers + * @param {String} adId - In the VAST tag, this value is present in the Ad element's id property. + * @param {String} adTagUrl - The ad tag url that was loaded into the player. + * @param {[String]} adWrapperIds - List of ad id's that were obtained from the different wrappers. Each redirect points to an ad wrapper. + * @return {bidIdentifier} - Object allowing the bid matching the ad event to be identified. + */ + +/** + * @typedef {Object} bidIdentifier + * @property {String} adId - Bid identifier. + * @property {String} adUnitCode - Identifier for the Ad Unit for which the bid was made. + * @property {String} auctionId - Id of the auction in which the bid was made. + * @property {String} requestId - Id of the bid request which resulted in the bid. + */ + +/** + * Factory function for obtaining a Video Impression Verifier. + * @param {Boolean} isCacheUsed - wether Prebid is configured to use a cache. + * @return {VideoImpressionVerifier} + */ +export function videoImpressionVerifierFactory(isCacheUsed) { + const vastXmlEditor = vastXmlEditorFactory(); + const bidTracker = tracker(); + if (isCacheUsed) { + return cachedVideoImpressionVerifier(vastXmlEditor, bidTracker); + } + + return videoImpressionVerifier(vastXmlEditor, bidTracker); +} + +export function videoImpressionVerifier(vastXmlEditor_, bidTracker_) { + const verifier = baseImpressionVerifier(bidTracker_); + const superTrackBid = verifier.trackBid; + const vastXmlEditor = vastXmlEditor_; + + verifier.trackBid = function(bid) { + let { vastXml, vastUrl } = bid; + if (!vastXml && !vastUrl) { + return; + } + + const uuid = superTrackBid(bid); + + if (vastUrl) { + const url = new URL(vastUrl); + url.searchParams.append(UUID_MARKER, uuid); + bid.vastUrl = url.toString(); + } else if (vastXml) { + bid.vastXml = vastXmlEditor.getVastXmlWithTracking(vastXml, uuid); + } + + return uuid; + } + + return verifier; +} + +export function cachedVideoImpressionVerifier(vastXmlEditor_, bidTracker_) { + const verifier = baseImpressionVerifier(bidTracker_); + const superTrackBid = verifier.trackBid; + const superGetBidIdentifiers = verifier.getBidIdentifiers; + const vastXmlEditor = vastXmlEditor_; + + verifier.trackBid = function (bid, globalAdUnits) { + const adIdOverride = superTrackBid(bid); + let { vastXml, vastUrl, adId, adUnitCode } = bid; + const adUnit = find(globalAdUnits, adUnit => adUnitCode === adUnit.code); + const videoConfig = adUnit && adUnit.video; + const adServerConfig = videoConfig && videoConfig.adServer; + const trackingConfig = adServerConfig && adServerConfig.tracking; + let impressionUrl; + let impressionId; + let errorUrl; + const impressionTracking = trackingConfig.impression; + const errorTracking = trackingConfig.error; + + if (impressionTracking) { + impressionUrl = getTrackingUrl(impressionTracking.getUrl, bid); + impressionId = impressionTracking.id || adId + '-impression'; + } + + if (errorTracking) { + errorUrl = getTrackingUrl(errorTracking.getUrl, bid); + } + + if (vastXml) { + vastXml = vastXmlEditor.getVastXmlWithTracking(vastXml, adIdOverride, impressionUrl, impressionId, errorUrl); + } else if (vastUrl) { + vastXml = vastXmlEditor.buildVastWrapper(adIdOverride, vastUrl, impressionUrl, impressionId, errorUrl); + } + + bid.vastXml = vastXml; + return adIdOverride; + } + + verifier.getBidIdentifiers = function (adId, adTagUrl, adWrapperIds) { + // When the video is cached, the ad tag loaded into the player is a parent wrapper of the cache url. + // As a result, the ad tag Url cannot include identifiers. + return superGetBidIdentifiers(adId, null, adWrapperIds); + } + + return verifier; + + function getTrackingUrl(getUrl, bid) { + if (!getUrl || typeof getUrl !== 'function') { + return; + } + + return getUrl(bid); + } +} + +export function baseImpressionVerifier(bidTracker_) { + const bidTracker = bidTracker_; + + function trackBid(bid) { + let { adId, adUnitCode, requestId, auctionId } = bid; + const trackingId = PB_PREFIX + generateUUID(10 ** 13); + bidTracker.store(trackingId, { adId, adUnitCode, requestId, auctionId }); + return trackingId; + } + + function getBidIdentifiers(adId, adTagUrl, adWrapperIds) { + return bidTracker.remove(adId) || getBidForAdTagUrl(adTagUrl) || getBidForAdWrappers(adWrapperIds); + } + + return { + trackBid, + getBidIdentifiers + }; + + function getBidForAdTagUrl(adTagUrl) { + if (!adTagUrl) { + return; + } + + let url; + try { + url = new URL(adTagUrl); + } catch (e) { + return; + } + + const queryParams = url.searchParams; + let uuid = queryParams.get(UUID_MARKER); + return uuid && bidTracker.remove(uuid); + } + + function getBidForAdWrappers(adWrapperIds) { + if (!adWrapperIds || !adWrapperIds.length) { + return; + } + + for (const wrapperId in adWrapperIds) { + const bidInfo = bidTracker.remove(wrapperId); + if (bidInfo) { + return bidInfo; + } + } + } +} + +export function tracker() { + const model = {}; + + function store(key, value) { + model[key] = value; + } + + function remove(key) { + const value = model[key]; + if (!value) { + return; + } + + delete model[key]; + return value; + } + + return { + store, + remove + } +} diff --git a/modules/videojsVideoProvider.js b/modules/videojsVideoProvider.js new file mode 100644 index 00000000000..cc17758bd72 --- /dev/null +++ b/modules/videojsVideoProvider.js @@ -0,0 +1,854 @@ +import { + SETUP_COMPLETE, SETUP_FAILED, DESTROYED, + PLAYLIST, PLAYBACK_REQUEST, CONTENT_LOADED, PLAY, PAUSE, TIME, SEEK_START, SEEK_END, MUTE, VOLUME, ERROR, COMPLETE, + FULLSCREEN, PLAYER_RESIZE, + AD_REQUEST, AD_IMPRESSION, AD_TIME, AD_COMPLETE, AD_SKIPPED, AD_CLICK, AD_STARTED, AD_ERROR, AD_LOADED, AD_PLAY, AD_PAUSE +} from '../libraries/video/constants/events.js'; +// missing events: , AD_BREAK_START, , AD_BREAK_END, VIEWABLE, BUFFER, CAST, PLAYLIST_COMPLETE, RENDITION_UPDATE, PLAY_ATTEMPT_FAILED, AUTOSTART_BLOCKED +import { + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION, PLAYBACK_END +} from '../libraries/video/constants/ortb.js'; +import { VIDEO_JS_VENDOR } from '../libraries/video/constants/vendorCodes.js'; +import { submodule } from '../src/hook.js'; +import stateFactory from '../libraries/video/shared/state.js'; +import { PLAYBACK_MODE } from '../libraries/video/constants/constants.js'; +import { getEventHandler } from '../libraries/video/shared/eventHandler.js'; + +/* +Plugins of interest: +https://www.npmjs.com/package/videojs-chromecast +https://www.npmjs.com/package/@silvermine/videojs-airplay +https://www.npmjs.com/package/videojs-airplay +https://www.npmjs.com/package/@silvermine/videojs-chromecast +https://www.npmjs.com/package/videojs-ima +https://github.com/googleads/videojs-ima +https://github.com/videojs/videojs-playlist +https://github.com/videojs/videojs-contrib-ads +https://github.com/videojs/videojs-errors +https://github.com/videojs/videojs-overlay +https://github.com/videojs/videojs-playlist-ui + +inspiration: +https://github.com/Conviva/conviva-js-videojs/blob/master/conviva-videojs-module.js + */ + +const setupFailMessage = 'Failed to instantiate the player'; +const AD_MANAGER_EVENTS = [AD_LOADED, AD_STARTED, AD_IMPRESSION, AD_PLAY, AD_PAUSE, AD_TIME, AD_COMPLETE, AD_SKIPPED]; + +export function VideojsProvider(config, vjs_, adState_, timeState_, callbackStorage_, utils) { + let vjs = vjs_; + // Supplied callbacks are typically wrapped by handlers + // we use this dict to keep track of these pairings + const callbackToHandler = {}; + + const adState = adState_; + const timeState = timeState_; + let player = null; + let playerVersion = null; + let playerIsSetup = false; + const {playerConfig, divId} = config; + let isMuted; + let previousLastTimePosition = 0; + let lastTimePosition = 0; + + let setupCompleteCallbacks = []; + let setupFailedCallbacks = []; + let setupFailedEventHandlers = []; + + // TODO: test with older videojs versions + let minimumSupportedPlayerVersion = '7.17.0'; + + function init() { + if (!vjs) { + triggerSetupFailure(-1, setupFailMessage + ': Videojs not present') + return; + } + + playerVersion = vjs.VERSION; + if (playerVersion < minimumSupportedPlayerVersion) { + triggerSetupFailure(-2, setupFailMessage + ': Videojs version not supported'); + return; + } + + if (!document.getElementById(divId)) { + triggerSetupFailure(-3, setupFailMessage + ': No div found with id ' + divId); + return; + } + + const instantiatedPlayers = vjs.players; + if (instantiatedPlayers && instantiatedPlayers[divId]) { + // already instantiated + player = instantiatedPlayers[divId]; + onReady(); + return; + } + + setupPlayer(playerConfig); + + if (!player) { + triggerSetupFailure(-4, setupFailMessage); + } + } + + function getId() { + return divId; + } + + function getOrtbVideo() { + if (!player) { + return; + } + + let playBackMethod = PLAYBACK_METHODS.CLICK_TO_PLAY; + // returns a boolean or a string with the autoplay strategy + const autoplay = player.autoplay(); + const muted = player.muted() || autoplay === 'muted'; + // check if autoplay is truthy since it may be a bool or string + if (autoplay) { + playBackMethod = muted ? PLAYBACK_METHODS.AUTOPLAY_MUTED : PLAYBACK_METHODS.AUTOPLAY; + } + const supportedMediaTypes = Object.values(VIDEO_MIME_TYPE).filter( + // Follows w3 spec https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype + type => player.canPlayType(type) !== '' + ) + + // IMA supports vpaid unless its expliclty turned off + // TODO: needs a reference to the imaOptions used at setup to determine if vpaid can be used + // if (imaOptions && imaOptions.vpaidMode !== 0) { + supportedMediaTypes.push(VPAID_MIME_TYPE); + // } + + const video = { + mimes: supportedMediaTypes, + // Based on the protocol support provided by the videojs-ima plugin + // https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/compatibility + // Need to check for the plugins + protocols: [ + PROTOCOLS.VAST_2_0, + ], + api: [ + API_FRAMEWORKS.VPAID_2_0 // TODO: needs a reference to the imaOptions used at setup to determine if vpaid can be used + ], + // TODO: Make sure this returns dimensions in DIPS + h: player.currentHeight(), + w: player.currentWidth(), + // TODO: implement startdelay since its reccomend param + // both linearity forms are supported so the param is excluded + // sequence - TODO not yet supported + maxextended: -1, + boxingallowed: 1, + playbackmethod: [ playBackMethod ], + playbackend: PLAYBACK_END.VIDEO_COMPLETION, + // Per ortb 7.4 skip is omitted since neither the player nor ima plugin imposes a skip button, or a skipmin/max + }; + + // TODO: Determine placement may not be in stream if videojs is only used to serve ad content + // ~ Sort of resolved check if the player has a source to tell if the placement is instream + // Still cannot reliably check what type of placement the player is if its outstream + // i.e. we can't tell if its interstitial, in article, etc. + if (player.src()) { + video.placement = PLACEMENT.INSTREAM; + } + + // Placement according to IQG Guidelines 4.2.8 + // https://cdn2.hubspot.net/hubfs/2848641/TrustworthyAccountabilityGroup_May2017/Docs/TAG-Inventory-Quality-Guidelines-v2_2-10-18-2016.pdf?t=1509469105938 + const findPosition = vjs.dom.findPosition; + if (player.isFullscreen()) { + video.pos = AD_POSITION.FULL_SCREEN; + } else if (findPosition) { + video.pos = utils.getPositionCode(findPosition(player.el())); + } + + return video; + } + + function getOrtbContent() { + if (!player) { + return; + } + + const content = { + // id:, TODO: find a suitable id for videojs sources + url: player.currentSrc() + }; + // Only include length if player is ready + // player.readyState() returns a level of readiness from 0 to 4 + // https://docs.videojs.com/player#readyState + if (player.readyState()) { + content.len = Math.round(player.duration()); + } + + const mediaItem = utils.getMedia(player); + if (mediaItem) { + for (let param of ['id', 'title', 'description', 'album', 'artist']) { + if (mediaItem[param]) { + content[param] = mediaItem[param]; + } + } + } + + const contentUrl = utils.getValidMediaUrl(mediaItem && mediaItem.src, player.src) + if (contentUrl) { + content.url = contentUrl; + } + + return content; + } + + // Plugins to integrate: https://github.com/googleads/videojs-ima + function setAdTagUrl(adTagUrl, options) { + if (!player.ima || !adTagUrl) { + return; + } + + player.ima.changeAdTag(adTagUrl); + player.ima.requestAds(); + } + + function onEvent(type, callback, payload) { + registerSetupListeners(type, callback, payload); + + if (!player) { + return; + } + + player.ready(() => { + registerListeners(type, callback, payload); + }); + } + + function registerSetupListeners(externalEventName, callback, basePayload) { + // no point in registering for setup failures if already setup. + if (playerIsSetup) { + return; + } + + if (externalEventName === SETUP_COMPLETE) { + setupCompleteCallbacks.push(callback); + } else if (externalEventName === SETUP_FAILED) { + setupFailedCallbacks.push(callback); + registerSetupErrorListener() + } + } + + function registerSetupErrorListener() { + if (!player) { + return + } + + const eventHandler = () => { + /* + Videojs has no specific setup error handler + so we imitate it by hooking to the general error + handler and checking to see if the player has been setup + */ + if (playerIsSetup) { + return; + } + + const error = player.error(); + triggerSetupFailure(error.code, error.message, error); + }; + + player.on(ERROR, eventHandler); + setupFailedEventHandlers.push(eventHandler) + } + + function registerListeners(externalEventName, callback, basePayload) { + if (externalEventName === MUTE) { + const eventHandler = () => { + if (isMuted !== player.muted()) { + basePayload.mute = isMuted = !isMuted; + callback(externalEventName, basePayload); + } + }; + player.on(utils.getVideojsEventName(VOLUME), eventHandler); + return; + } + + let getEventPayload; + + switch (externalEventName) { + case PLAY: + case PAUSE: + case DESTROYED: + break; + + case PLAYBACK_REQUEST: + getEventPayload = e => ({ playReason: 'unknown' }); + break; + + case AD_REQUEST: + getEventPayload = e => { + const adTagUrl = e.AdsRequest.adTagUrl; + adState.updateState({ adTagUrl }); + return { adTagUrl }; + }; + break + + case AD_LOADED: + getEventPayload = (e) => { + const imaAd = e.getAdData && e.getAdData(); + adState.updateForEvent(imaAd); + timeState.clearState(); + return adState.getState(); + }; + break + + case AD_STARTED: + case AD_PLAY: + case AD_PAUSE: + getEventPayload = () => adState.getState(); + break + + case AD_IMPRESSION: + case AD_CLICK: + getEventPayload = () => Object.assign({}, adState.getState(), timeState.getState()); + break + + case AD_TIME: + getEventPayload = (e) => { + const adTimeEvent = e && e.getAdData && e.getAdData(); + timeState.updateForTimeEvent(adTimeEvent); + return Object.assign({}, adState.getState(), timeState.getState()); + }; + break + + case AD_COMPLETE: + getEventPayload = () => { + const currentState = adState.getState(); + adState.clearState(); + return currentState; + }; + break + + case AD_SKIPPED: + getEventPayload = () => { + const currentState = Object.assign({}, adState.getState(), timeState.getState()); + adState.clearState(); + return currentState; + }; + break + + case AD_ERROR: + getEventPayload = e => { + const imaAdError = e.data && e.data.AdError; + const extraPayload = Object.assign({ + playerErrorCode: imaAdError.getErrorCode(), + vastErrorCode: imaAdError.getVastErrorCode(), + errorMessage: imaAdError.getMessage(), + sourceError: imaAdError.getInnerError() + // timeout + }, adState.getState(), timeState.getState()); + adState.clearState(); + return extraPayload; + }; + break + + case PLAYLIST: + getEventPayload = e => ({ + playlistItemCount: utils.getPlaylistCount(player), + autostart: player.autoplay() + }); + break + + case CONTENT_LOADED: + getEventPayload = e => { + const media = utils.getMedia(player); + const contentUrl = utils.getValidMediaUrl(media && media.src, player.src, e && e.target && e.target.currentSrc) + return { + contentId: media && media.id, + contentUrl, + title: media && media.title, + description: media && media.description, + playlistIndex: utils.getCurrentPlaylistIndex(player), + contentTags: media && media.contentTags + }; + }; + break; + + case TIME: + // TODO: might want to check seeking() and/or scrubbing() + getEventPayload = e => { + previousLastTimePosition = lastTimePosition; + const currentTime = player.currentTime(); + const duration = player.duration(); + timeState.updateForTimeEvent({ currentTime, duration }); + lastTimePosition = currentTime; + return { + position: lastTimePosition, + duration + }; + }; + break; + + case SEEK_START: + getEventPayload = e => { + return { + position: previousLastTimePosition, + destination: player.currentTime(), + duration: player.duration() + }; + } + break; + + case SEEK_END: + getEventPayload = () => ({ + position: player.currentTime(), + duration: player.duration() + }); + break; + + case VOLUME: + getEventPayload = e => ({ volumePercentage: player.volume() * 100 }); + break; + + case ERROR: + getEventPayload = e => { + const error = player.error(); + return { + sourceError: error, + errorCode: error.code, + errorMessage: error.message, + }; + }; + break; + + case COMPLETE: + getEventPayload = e => { + previousLastTimePosition = lastTimePosition = 0; + timeState.clearState(); + }; + break; + + case FULLSCREEN: + getEventPayload = e => ({ fullscreen: player.isFullscreen() }); + break; + + case PLAYER_RESIZE: + getEventPayload = e => ({ + height: player.currentHeight(), + width: player.currentWidth(), + }); + break; + + default: + return; + } + + const eventHandler = getEventHandler(externalEventName, callback, basePayload, getEventPayload); + + if (externalEventName === PLAYLIST) { + registerPlaylistEventListener(eventHandler); + return; + } + + const videojsEventName = utils.getVideojsEventName(externalEventName); + + if (AD_MANAGER_EVENTS.includes(externalEventName)) { + player.on('ads-manager', () => player.ima.addEventListener(videojsEventName, eventHandler)); + } else { + player.on(videojsEventName, eventHandler); + } + } + + function registerPlaylistEventListener(eventHandler) { + if (player.playlist) { + // force a playlist event on first item load + player.one('loadstart', eventHandler); + player.on('playlistchange', eventHandler); + } else { + // When playlist plugin is not used, treat each media item as a single item playlist + player.on('loadstart', eventHandler); + } + } + + function offEvent(event, callback) { + const videojsEvent = utils.getVideojsEventName(event) + if (!callback) { + player.off(videojsEvent); + return; + } + + const eventHandler = callbackToHandler[event];// callbackStorage.getCallback(event, callback); + if (eventHandler) { + player.off(videojsEvent, eventHandler); + } + } + + function destroy() { + if (!player) { + return; + } + player.remove(); + player = null; + } + + return { + init, + getId, + getOrtbVideo, + getOrtbContent, + setAdTagUrl, + onEvent, + offEvent, + destroy + }; + + function setupPlayer(config) { + const setupConfig = utils.getSetupConfig(config); + player = vjs(divId, setupConfig, onReady); + } + + function onReady() { + try { + setupAds(); + } catch (e) { + triggerSetupFailure(-5, e.message); + return; + } + triggerSetupComplete(); + } + + // TODO: consider supporting https://www.npmjs.com/package/videojs-vast-vpaid as well + function setupAds() { + if (!player.ima) { + throw new Error(setupFailMessage + ': ima plugin is missing'); + } + + if (typeof player.ima !== 'function') { + // when player.ima is already instantiated, it is an object. Early abort if already instantiated. + return; + } + + const adConfig = utils.getAdConfig(config); + player.ima(adConfig); + } + + function triggerSetupFailure(errorCode, msg, sourceError) { + const payload = { + divId, + playerVersion, + type: SETUP_FAILED, + errorCode, + errorMessage: msg, + sourceError: sourceError + }; + setupFailedCallbacks.forEach(setupFailedCallback => setupFailedCallback(SETUP_FAILED, payload)); + setupFailedCallbacks = []; + } + + function triggerSetupComplete() { + playerIsSetup = true; + const payload = { + divId, + playerVersion, + type: SETUP_COMPLETE, + }; + + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; + + isMuted = player.muted(); + + setupFailedEventHandlers.forEach(eventHandler => player.off('error', eventHandler)); + setupFailedEventHandlers = []; + } +} + +export const utils = { + getSetupConfig: function (config) { + if (!config) { + return; + } + + const params = config.params || {}; + const videojsConfig = params.vendorConfig || {}; + + if (videojsConfig.autostart === undefined && config.autostart !== undefined) { + videojsConfig.autostart = config.autostart + } + + if (videojsConfig.muted === undefined && config.mute !== undefined) { + videojsConfig.muted = config.mute; + } + + return videojsConfig; + }, + + getAdConfig: function (config) { + const params = config && config.params; + if (!params) { + return {}; + } + + return params.adPluginConfig || {}; // TODO: add adPluginConfig to spec + }, + + getPositionCode: function({left, top, width, height}) { + const bottom = window.innerHeight - top - height; + const right = window.innerWidth - left - width; + + if (left < 0 || right < 0 || top < 0) { + return AD_POSITION.UNKNOWN; + } + + return bottom >= 0 ? AD_POSITION.ABOVE_THE_FOLD : AD_POSITION.BELOW_THE_FOLD; + }, + + getVideojsEventName: function(eventName) { + switch (eventName) { + case SETUP_COMPLETE: + return 'ready'; + case SETUP_FAILED: + return 'error'; + case DESTROYED: + return 'dispose'; + case AD_REQUEST: + return 'ads-request'; + case AD_LOADED: + return 'loaded' + case AD_STARTED: + return 'start'; + case AD_IMPRESSION: + return 'impression'; + case AD_PLAY: + return 'resume' + case AD_PAUSE: + return PAUSE; + case AD_TIME: + return 'adProgress'; + case AD_CLICK: + return 'click'; + case AD_COMPLETE: + return COMPLETE; + case AD_SKIPPED: + return 'skip'; + case AD_ERROR: + return 'adserror'; + case CONTENT_LOADED: + return 'loadstart'; + case PLAY: + return PLAY + 'ing'; + case PLAYBACK_REQUEST: + return PLAY; + case SEEK_START: + return 'seeking'; + case SEEK_END: + return 'seeked'; + case TIME: + return TIME + 'update'; + case VOLUME: + return VOLUME + 'change'; + case MUTE: + return MUTE + 'change'; + case PLAYER_RESIZE: + return 'playerresize'; + case FULLSCREEN: + return FULLSCREEN + 'change'; + case COMPLETE: + return 'ended'; + default: + return eventName; + } + /* + The following video.js events might map to an event in our spec + 'loadstart', + 'progress', buffer load ? + 'suspend', + 'abort', + 'error', + 'emptied', + 'stalled', + 'loadedmetadata', meta + 'loadeddata', meta + 'canplay', + 'canplaythrough', + 'waiting', buffer? + 'durationchange', meta-duration + 'ratechange', + */ + }, + + getMedia: function(player) { + const playlistItem = this.getCurrentPlaylistItem(player); + if (playlistItem) { + return playlistItem.sources[0]; + } + + return player.getMedia(); + }, + + getValidMediaUrl: function(mediaSrc, playerSrc, eventTargetSrc) { + return this.getMediaUrl(mediaSrc) || this.getMediaUrl(playerSrc) || this.getMediaUrl(eventTargetSrc); + }, + + getMediaUrl: function(source) { + if (!source) { + return; + } + + if (Array.isArray(source) && source.length) { + return this.parseSource(source[0]); + } + + return this.parseSource(source) + }, + + parseSource: function (source) { + const type = typeof source; + if (type === 'string') { + return source; + } else if (type === 'object') { + return source.src; + } + }, + + getPlaylistCount: function (player) { + const playlist = player.playlist; // has playlist plugin + if (!playlist) { + return 1; + } + return playlist.lastIndex && playlist.lastIndex() + 1; + }, + + getCurrentPlaylistIndex: function (player) { + const playlist = player.playlist; // has playlist plugin + if (!playlist) { + return 0; + } + return playlist.currentIndex && playlist.currentIndex(); + }, + + getCurrentPlaylistItem: function(player) { + const playlist = player.playlist; // has playlist plugin + if (!playlist) { + return; + } + + const currentIndex = this.getCurrentPlaylistIndex(player); + if (!currentIndex) { + return + } + + const item = playlist()[currentIndex]; + return item; + } +}; + +const videojsSubmoduleFactory = function (config) { + const adState = adStateFactory(); + const timeState = timeStateFactory(); + const callbackStorage = null; + // videojs factory is stored to window by default + const vjs = window.videojs; + return VideojsProvider(config, vjs, adState, timeState, callbackStorage, utils); +} + +videojsSubmoduleFactory.vendorCode = VIDEO_JS_VENDOR; +submodule('video', videojsSubmoduleFactory); +export default videojsSubmoduleFactory; + +// STATE + +/** + * @returns {State} + */ +export function adStateFactory() { + const adState = Object.assign({}, stateFactory()); + + function updateForEvent(event) { + if (!event) { + return; + } + + const skippable = event.skippable; + // TODO: possibly can check traffickingParameters to determine if winning bid is passed + const updates = { + adId: event.adId, + adServer: event.adSystem, + advertiserName: event.advertiserName, + redirectUrl: event.clickThroughUrl, + creativeId: event.creativeId || event.creativeAdId, + dealId: event.dealId, + adDescription: event.description, + linear: event.linear, + creativeUrl: event.mediaUrl, + adTitle: event.title, + universalAdId: event.universalAdIdValue, + creativeType: event.contentType, + wrapperAdIds: event.adWrapperIds, + skip: skippable ? 1 : 0, + // missing fields: + // loadTime + // advertiserId - TODO: does this even exist ? If not, remove from spec + // vastVersion + // adCategories + // campaignId + // waterfallIndex + // waterfallCount + // skipmin + // adTagUrl - for now, only has request ad tag + // adPlacementType + }; + + const adPodInfo = event.adPodInfo; + if (adPodInfo && adPodInfo.podIndex > -1) { + updates.adPodCount = adPodInfo.totalAds; + updates.adPodIndex = adPodInfo.adPosition - 1; // Per IMA docs, adPosition is 1 based. + } + + if (adPodInfo && adPodInfo.timeOffset) { + switch (adPodInfo.timeOffset) { + case -1: + updates.offset = 'post'; + break + + case 0: + // TODO: Defaults to 0 if this ad is not part of a pod, or the pod is not part of an ad playlist. - need to check if loaded dynamically and pass last content time update + updates.offset = 'pre'; + break + + default: + updates.offset = '' + adPodInfo.timeOffset; + } + } + + if (skippable) { + updates.skipafter = event.skipTimeOffset; + } + + this.updateState(updates); + } + + adState.updateForEvent = updateForEvent; + + return adState; +} + +export function timeStateFactory() { + const timeState = Object.assign({}, stateFactory()); + + function updateForTimeEvent(event) { + const { currentTime, duration } = event; + this.updateState({ + time: currentTime, + duration, + playbackMode: getPlaybackMode(duration) + }); + } + + timeState.updateForTimeEvent = updateForTimeEvent; + + function getPlaybackMode(duration) { + if (duration > 0) { + return PLAYBACK_MODE.VOD; + } else if (duration < 0) { + return PLAYBACK_MODE.DVR; + } + + return PLAYBACK_MODE.LIVE; + } + + return timeState; +} diff --git a/modules/videojsVideoProvider.md b/modules/videojsVideoProvider.md new file mode 100644 index 00000000000..70f4daa64e1 --- /dev/null +++ b/modules/videojsVideoProvider.md @@ -0,0 +1,17 @@ +# Overview + +Module Name: Video.js Video Provider +Module Type: Video Submodule +Video Player: video.js +Player website: https://videojs.com/ +Maintainer: Prebid Video Task Force + +# Description + +Video provider to connect the Prebid Video Module to video.js. + +# Requirements + +- Your page must include a build of video.js. For instructions see https://videojs.com/getting-started . +- Your video.js instance must include the Google IMA plugin. + - The Google IMA plugin must be accessible publicly in order for the Video.js Video Provider to reference it. diff --git a/modules/viouslyBidAdapter.js b/modules/viouslyBidAdapter.js new file mode 100644 index 00000000000..f18c9c7b3db --- /dev/null +++ b/modules/viouslyBidAdapter.js @@ -0,0 +1,209 @@ +import { deepAccess, logError, parseUrl, parseSizesInput, triggerPixel } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; // eslint-disable-line prebid/validate-imports + +const BIDDER_CODE = 'viously'; +// const GVLID = 1028; +const CURRENCY = 'EUR'; +const TTL = 60; +const HTTP_METHOD = 'POST'; +const REQUEST_URL = 'https://bidder.viously.com/bid'; +const REQUIRED_VIDEO_PARAMS = ['context', 'playbackmethod', 'playerSize']; +const REQUIRED_VIOUSLY_PARAMS = ['pid']; + +export const spec = { + code: BIDDER_CODE, + // gvlid: GVLID, + supportedMediaTypes: [VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + let videoParams = deepAccess(bid, 'mediaTypes.video'); + + if (!bid.params) { + logError('The bid params are missing'); + return false; + } + + if (!videoParams) { + logError('The placement must be of video type'); + return false; + } + + /** + * VIDEO checks + */ + + let areParamsValid = true; + + REQUIRED_VIDEO_PARAMS.forEach(function(videoParam) { + if (typeof videoParams[videoParam] === 'undefined') { + logError('mediaTypes.video.' + videoParam + ' must be set for video placement.'); + areParamsValid = false; + } + }); + + if (parseSizesInput(videoParams.playerSize).length === 0) { + logError('mediaTypes.video.playerSize must be set for video placement at the right format.'); + return false; + } + + /** + * Viously checks + */ + + REQUIRED_VIOUSLY_PARAMS.forEach(function(viouslyParam) { + if (typeof bid.params[viouslyParam] === 'undefined') { + logError('The ' + viouslyParam + ' is missing.'); + areParamsValid = false; + } + }); + + if (!areParamsValid) { + return false; + } + + return true; + }, + + buildRequests: function(validBidRequests, bidderRequest) { + let payload = {}; + + /** Viously Publisher ID */ + if (validBidRequests[0].params.pid) { + payload.pid = validBidRequests[0].params.pid; + } + + // Referer Info + if (config.getConfig('pageUrl')) { + let parsedUrl = parseUrl(config.getConfig('pageUrl')); + + payload.domain = parsedUrl.hostname; + payload.page_domain = config.getConfig('pageUrl'); + } else if (bidderRequest && bidderRequest.refererInfo) { + let parsedUrl = parseUrl(bidderRequest.refererInfo.page); + + payload.domain = parsedUrl.hostname; + payload.page_domain = bidderRequest.refererInfo.page; + } + if (payload.domain) { + /** Make sur that the scheme is not part of the domain */ + payload.domain = payload.domain.replace(/(^\w+:|^)\/\//, ''); + payload.domain = payload.domain.replace(/\/$/, ''); + } + + // Handle GDPR + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; + payload.gdpr_consent = bidderRequest.gdprConsent.consentString; + if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; + } + } + + // US Privacy + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + + // Schain + if (validBidRequests[0].schain) { + payload.schain = validBidRequests[0].schain; + } + // Currency + payload.currency_code = CURRENCY; + + // User IDs + if (validBidRequests[0].userIdAsEids) { + payload.users_uid = validBidRequests[0].userIdAsEids; + } + + // Placements + payload.placements = validBidRequests.map(bidRequest => { + let request = { + id: bidRequest.adUnitCode, + bid_id: bidRequest.bidId + }; + + request.video_params = { + context: deepAccess(bidRequest, 'mediaTypes.video.context'), + playbackmethod: deepAccess(bidRequest, 'mediaTypes.video.playbackmethod'), + size: parseSizesInput(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) + }; + + return request; + }); + + return { + method: HTTP_METHOD, + url: validBidRequests[0].params.endpoint ? validBidRequests[0].params.endpoint : REQUEST_URL, + data: payload + }; + }, + + interpretResponse: function(serverResponse, requests) { + const bidResponses = []; + const responseBody = serverResponse.body; + + if (responseBody.ads && responseBody.ads.length > 0) { + responseBody.ads.forEach(function(bidResponse) { + if (bidResponse.bid) { + let bidRequest = find(requests.data.placements, bid => bid.bid_id === bidResponse.bid_id); + + if (bidRequest) { + let sizes = bidResponse.size.split('x'); + + const bid = { + requestId: bidRequest.bid_id, + id: bidResponse.id, + cpm: bidResponse.cpm, + width: sizes[0], + height: sizes[1], + creativeId: bidResponse.creative_id || '', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'video', + meta: {}, + // Tracking data + nurl: bidResponse.nurl ? bidResponse.nurl : [] + }; + + if (bidResponse.ad_url) { + bid.vastUrl = bidResponse.ad_url; + } else { + bid.vastXml = bidResponse.ad; + } + + bidResponses.push(bid); + } + } + }); + } + + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {}, + + onTimeout: function(timeoutData) {}, + + onBidWon: function(bid) { + if (bid && bid.nurl && bid.nurl.length > 0) { + bid.nurl.forEach(function(winUrl) { + triggerPixel(winUrl, null); + }); + } + }, + + onSetTargeting: function(bid) {} +}; + +registerBidder(spec); diff --git a/modules/viouslyBidAdapter.md b/modules/viouslyBidAdapter.md new file mode 100644 index 00000000000..0d15525cc72 --- /dev/null +++ b/modules/viouslyBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Viously Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@viously.com +``` + +# Description + +Module that connects to Viously's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + playerSize: [640, 360], + context: 'instream', + playbackmethod: [1, 2, 3, 4, 5, 6] + } + }, + bids: [ + { + bidder: 'viously', + params: { + pid: '20d30b78-43ec-11ed-b878-0242ac120002' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/viqeoBidAdapter.js b/modules/viqeoBidAdapter.js new file mode 100644 index 00000000000..5762a794c8e --- /dev/null +++ b/modules/viqeoBidAdapter.js @@ -0,0 +1,180 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {logError, logInfo, _each, mergeDeep, isFn, isNumber, isPlainObject} from '../src/utils.js' +import {VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; + +const BIDDER_CODE = 'viqeo'; +const DEFAULT_MIMES = ['application/javascript']; +const VIQEO_ENDPOINT = 'https://ads.betweendigital.com/openrtb_bid'; +const RENDERER_URL = 'https://cdn.viqeo.tv/js/vq_starter.js'; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_SSPID = 44697; + +function getBidFloor(bid) { + const {floor, currency} = bid.params; + const curr = currency || DEFAULT_CURRENCY; + if (!isFn(bid.getFloor)) { + return {floor: isNumber(floor) ? floor : 0, currency: curr}; + } + const floorInfo = bid.getFloor({currency: curr, mediaType: VIDEO, size: '*'}); + if (isPlainObject(floorInfo) && isNumber(floorInfo.floor) && floorInfo.currency === curr) { + return floorInfo; + } + return {floor: floor || 0, currency: currency || DEFAULT_CURRENCY}; +} + +function getVideoTargetingParams({mediaTypes: {video}}) { + const result = {}; + Object.keys(Object(video)) + .forEach(key => { + if (key === 'playerSize') { + result.w = video.playerSize[0][0]; + result.h = video.playerSize[0][1]; + } else if (key !== 'context') { + result[key] = video[key]; + } + }) + return result; +} + +/** + * @type {BidderSpec} + */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + /** + * @param {BidRequest} bidRequest The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: ({params}) => { + if (!params) { + logError('failed validation: params not declared'); + return false; + } + if (!params.user && !params.user?.buyeruid) { + logError('failed validation: user.buyeruid not declared'); + return false; + } + if (!params.playerOptions) { + logError('failed validation: playerOptions not declared'); + return false; + } + const {profileId, videoId, playerId} = params.playerOptions; + if (!profileId) { + logError('failed validation: profileId not declared'); + return false; + } + if (!videoId && !playerId) { + logError('failed validation: videoId or playerId not declared'); + return false; + } + return true; + }, + /** + * @param validBidRequests {BidRequest[]} + * @returns {ServerRequest[]} + */ + buildRequests: (validBidRequests) => { + logInfo('validBidRequests', validBidRequests); + const bidRequests = []; + _each(validBidRequests, (bid, i) => { + const { + params: {test, sspId, endpointUrl}, + mediaTypes: {video}, + } = bid; + const ortb2 = bid.ortb2 || {}; + const user = bid.params.user || {}; + const device = bid.params.device || {}; + const site = bid.params.site || {}; + const w = window; + const floorInfo = getBidFloor(bid); + const data = { + id: bid.bidId, + test, + imp: [{ + id: `${i}`, + tagid: bid.adUnitCode, + video: { + ...getVideoTargetingParams(bid), + mimes: video.mimes || DEFAULT_MIMES, + }, + bidfloor: floorInfo.floor, + bidfloorcur: floorInfo.currency, + secure: 1 + }], + site: test === 1 ? { + page: 'https://viqeo.tv', + domain: 'viqeo.tv' + } : mergeDeep({ + domain: w.location.hostname, + page: w.location.href + }, ortb2.site, site), + device: mergeDeep({ + w: w.screen.width, + h: w.screen.height, + ua: w.navigator.userAgent, + }, ortb2.device, device), + user: mergeDeep({...user}, ortb2.user), + app: bid.params.app, + }; + bidRequests.push({ + url: endpointUrl || `${VIQEO_ENDPOINT}/?sspId=${sspId || DEFAULT_SSPID}`, + method: 'POST', + data, + bids: validBidRequests, + }); + }); + return bidRequests; + }, + /** + * @param {ServerResponse} serverResponse + * @param {BidRequest} bidRequests + * @return {Bid[]} + */ + interpretResponse: (serverResponse, bidRequests) => { + logInfo('serverResponse', serverResponse); + const bidResponses = []; + if (!serverResponse || !serverResponse.body) { + logError('empty response'); + return []; + } + try { + const {id, seatbid, cur} = serverResponse.body; + _each(seatbid, (sb) => { + const {bid} = sb; + _each(bid, (b) => { + const bidRequest = bidRequests.bids.find(({bidId}) => bidId === id); + const renderer = Renderer.install({ + url: bidRequest?.params?.renderUrl || RENDERER_URL, + }); + renderer.setRender((bid) => { + if (window.VIQEO) { + window.VIQEO.renderPrebid(bid); + } else { + logError('failed get window.VIQEO'); + } + }); + bidResponses.push({ + requestId: id, + currency: cur, + cpm: b.price, + ttl: b.exp, + netRevenue: true, + creativeId: b.cid, + width: b.w || bidRequest?.mediaTypes[VIDEO].playerSize[0][0], + height: b.h || bidRequest?.mediaTypes[VIDEO].playerSize[0][1], + vastXml: b.adm, + vastUrl: b.nurl, + mediaType: VIDEO, + renderer, + }) + }) + }); + } catch (error) { + logError(error); + } + return bidResponses; + }, +} +registerBidder(spec); diff --git a/modules/viqeoBidAdapter.md b/modules/viqeoBidAdapter.md new file mode 100644 index 00000000000..b4a020bf057 --- /dev/null +++ b/modules/viqeoBidAdapter.md @@ -0,0 +1,56 @@ +# Overview + +**Module Name**: Viqeo Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: muravjovv1@gmail.com + +# Description + +Viqeo Bidder Adapter for Prebid.js. About: https://viqeo.tv/ + +### Bid params + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|-----------------------------|----------|----------------------------------------------------------------------------------------------------------------------------|--------------------------|-----------| +| `user` | required | The object containing user data (See OpenRTB spec) | `user: {}` | `object` | +| `user.buyeruid` | required | User id | `"12345"` | `string` | +| `playerOptions` | required | The object containing Viqeo player options | `playerOptions: {}` | `object` | +| `playerOptions.profileId` | required | Viqeo profile id | `1382` | `number` | +| `playerOptions.videId` | optional | Viqeo video id | `"ed584da454c7205ca7e4"` | `string` | +| `playerOptions.playerId` | optional | Viqeo player id | `1` | `number` | +| `device` | optional | The object containing device data (See OpenRTB spec) | `device: {}` | `object` | +| `site` | optional | The object containing site data (See OpenRTB spec) | `site: {}` | `object` | +| `app` | optional | The object containing app data (See OpenRTB spec) | `app: {}` | `object` | +| `floor` | optional | Bid floor price | `0.5` | `number` | +| `currency` | optional | 3-letter ISO 4217 code defining the currency of the bid. | `EUR` | `string` | +| `test` | optional | Flag which will induce a sample bid response when true; only set to true for testing purposes (1 = true, 0 = false) | `1` | `integer` | +| `sspId` | optional | For debug, request id | `1` | `number` | +| `renderUrl` | optional | For debug, script player url | `"https://viqeo.tv"` | `string` | +| `endpointUrl` | optional | For debug, api endpoint | `"https://viqeo.tv"` | `string` | + +# Test Parameters +``` + var adUnits = [{ + code: 'your-slot', // use exactly the same code as your slot div id. + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'viqeo', + params: { + user: { + buyeruid: '1', + }, + playerOptions: { + videoId: 'ed584da454c7205ca7e4', + profileId: 1382, + }, + test: 1, + } + }] + }]; +``` diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index 36276973c20..301d278493b 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -27,6 +27,11 @@ export const spec = { let ccpa = ''; let coppa = 0; let tmax = 0; + let eids = []; + + if (bidRequests[0].userIdAsEids && bidRequests[0].userIdAsEids.length > 0) { + eids = bidRequests[0].userIdAsEids; + } if (bid && bid.gdprConsent) { gdprApplies = bid.gdprConsent.gdprApplies ? 1 : 0; @@ -74,7 +79,8 @@ export const spec = { }, user: { ext: { - consent: gdprConsent + consent: gdprConsent, + eids: eids } } }; diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 533e669fcaa..12e8d4b23c8 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -7,83 +7,98 @@ * @requires module:modules/realTimeData */ -/** +/** profile metadata * @typedef dataCallbackMetadata - * @property {Boolean} user if true it is user-centric data - * @property {String} source describe the source of data, if "contextual" or "wam" - * @property {Boolean} isDefault if true it the default profile defined in the configuration + * @property {boolean} user if true it is user-centric data + * @property {string} source describe the source of data, if 'contextual' or 'wam' + * @property {boolean} isDefault if true it the default profile defined in the configuration + */ + +/** profile from contextual, wam or sfbx + * @typedef {Object.} Profile */ /** onData callback type * @callback dataCallback - * @param {Object} data profile data + * @param {Profile} data profile data * @param {dataCallbackMetadata} meta metadata * @returns {void} */ /** setPrebidTargeting callback type * @callback setPrebidTargetingCallback - * @param {String} adUnitCode - * @param {Object} data + * @param {string} adUnitCode + * @param {Profile} data * @param {dataCallbackMetadata} metadata - * @returns {Boolean} + * @returns {boolean} */ /** sendToBidders callback type * @callback sendToBiddersCallback * @param {Object} bid - * @param {String} adUnitCode - * @param {Object} data + * @param {string} adUnitCode + * @param {Profile} data * @param {dataCallbackMetadata} metadata - * @returns {Boolean} + * @returns {boolean} */ /** * @typedef {Object} ModuleParams - * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) - * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) + * @property {?setPrebidTargetingCallback|?boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?dataCallback} onData callback * @property {?WeboCtxConf} weboCtxConf site-centric contextual configuration * @property {?WeboUserDataConf} weboUserDataConf user-centric wam configuration * @property {?SfbxLiteDataConf} sfbxLiteDataConf site-centric lite configuration */ +/** + * @callback assetIDcallback + * @returns {string} should return asset identifier using the form datasource:docId + */ /** * @typedef {Object} WeboCtxConf * @property {string} token required token to be used on bigsea contextual API requests * @property {?string} targetURL specify the target url instead use the referer - * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) - * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) + * @property {?assetIDcallback|?string} assetID specifies the assert identifier using the form datasource:docId or via callback + * @property {?setPrebidTargetingCallback|?boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?dataCallback} onData callback - * @property {?object} defaultProfile to be used if the profile is not found - * @property {?Boolean} enabled if false, will ignore this configuration + * @property {?Profile} defaultProfile to be used if the profile is not found + * @property {?boolean} enabled if false, will ignore this configuration * @property {?string} baseURLProfileAPI to be used to point to a different domain than ctx.weborama.com */ /** * @typedef {Object} WeboUserDataConf * @property {?number} accountId wam account id - * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) - * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) - * @property {?object} defaultProfile to be used if the profile is not found + * @property {?setPrebidTargetingCallback|?boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) + * @property {?Profile} defaultProfile to be used if the profile is not found * @property {?dataCallback} onData callback * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is 'webo_wam2gam_entry') - * @property {?Boolean} enabled if false, will ignore this configuration + * @property {?boolean} enabled if false, will ignore this configuration */ /** * @typedef {Object} SfbxLiteDataConf - * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) - * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) - * @property {?object} defaultProfile to be used if the profile is not found + * @property {?setPrebidTargetingCallback|?boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) + * @property {?Profile} defaultProfile to be used if the profile is not found * @property {?dataCallback} onData callback * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is '_lite') - * @property {?Boolean} enabled if false, will ignore this configuration + * @property {?boolean} enabled if false, will ignore this configuration */ + +/** common configuration between contextual, wam and sfbx + * @typedef {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} CommonConf + */ + import { getGlobal } from '../src/prebidGlobal.js'; import { + deepClone, deepSetValue, isEmpty, isFn, @@ -93,8 +108,9 @@ import { isStr, isBoolean, isPlainObject, - deepClone, - tryAppendQueryString, mergeDeep, logWarn + logWarn, + mergeDeep, + tryAppendQueryString } from '../src/utils.js'; import { submodule @@ -127,431 +143,869 @@ const WEBO_CTX_CONF_SECTION = 'weboCtxConf'; const WEBO_USER_DATA_CONF_SECTION = 'weboUserDataConf'; /** @type {string} */ const SFBX_LITE_DATA_CONF_SECTION = 'sfbxLiteDataConf'; - +/** @type {string} */ +const WEBO_CTX_SOURCE_LABEL = 'contextual'; +/** @type {string} */ +const WEBO_USER_DATA_SOURCE_LABEL = 'wam'; +/** @type {string} */ +const SFBX_LITE_DATA_SOURCE_LABEL = 'lite'; /** @type {number} */ const GVLID = 284; -/** @type {?Object} */ + export const storage = getStorageManager({ gvlid: GVLID, moduleName: SUBMODULE_NAME }); -/** @type {null|Object} */ -let _weboContextualProfile = null; +/** + * @typedef {Object} Component + * @property {boolean} initialized + * @property {?Profile} data + * @property {boolean} user if true it is user-centric data + * @property {string} source describe the source of data, if 'contextual' or 'wam' + * @property {buildProfileHandlerCallbackBuilder} callbackBuilder + */ + +/** + * @typedef {Object} Components + * @property {Component} WeboCtx + * @property {Component} WeboUserData + * @property {Component} SfbxLiteData + */ -/** @type {Boolean} */ -let _weboCtxInitialized = false; +/** + * @classdesc Weborama Real Time Data Provider + * @class + */ +class WeboramaRtdProvider { + #components; + name = SUBMODULE_NAME; + /** + * @param {Components} components + */ + constructor(components) { + this.#components = components; + } + /** Initialize module + * @method + * @param {Object} moduleConfig + * @param {?ModuleParams} moduleConfig.params + * @return {boolean} true if module was initialized with success + */ + init(moduleConfig) { + /** @type {Object} */ + const globalDefaults = { + setPrebidTargeting: true, + sendToBidders: true, + onData: () => { + /* do nothing */ + } + }; + /** @type {ModuleParams} */ + const moduleParams = Object.assign({}, globalDefaults, moduleConfig?.params || {}); -/** @type {?Object} */ -let _weboUserDataUserProfile = null; + // reset profiles -/** @type {Boolean} */ -let _weboUserDataInitialized = false; + this.#components.WeboCtx.data = null; + this.#components.WeboUserData.data = null; + this.#components.SfbxLiteData.data = null; -/** @type {?Object} */ -let _sfbxLiteDataProfile = null; + this.#components.WeboCtx.initialized = this.#initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, 'token'); + this.#components.WeboUserData.initialized = this.#initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION); + this.#components.SfbxLiteData.initialized = this.#initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION); -/** @type {Boolean} */ -let _sfbxLiteDataInitialized = false; + return Object.values(this.#components).some((c) => c.initialized); + } -/** Initialize module - * @param {object} moduleConfig - * @return {Boolean} true if module was initialized with success - */ -function init(moduleConfig) { - const moduleParams = moduleConfig?.params || {}; + /** function that will allow RTD sub-modules to modify the AdUnit object for each auction + * @method + * @param {Object} reqBidsConfigObj + * @param {doneCallback} onDone + * @param {Object} moduleConfig + * @param {?ModuleParams} moduleConfig.params + * @returns {void} + */ + getBidRequestData(reqBidsConfigObj, onDone, moduleConfig) { + /** @type {ModuleParams} */ + const moduleParams = moduleConfig?.params || {}; + + if (!this.#components.WeboCtx.initialized) { + this.#handleBidRequestData(reqBidsConfigObj, moduleParams); - _weboContextualProfile = null; - _weboUserDataUserProfile = null; - _sfbxLiteDataProfile = null; + onDone(); - _weboCtxInitialized = initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, 'token'); - _weboUserDataInitialized = initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION); - _sfbxLiteDataInitialized = initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION); + return; + } - return _weboCtxInitialized || _weboUserDataInitialized || _sfbxLiteDataInitialized; -} + /** @type {WeboCtxConf} */ + const weboCtxConf = moduleParams.weboCtxConf || {}; -/** Initialize subsection module - * @param {Object} moduleParams - * @param {string} subSection subsection name to initialize - * @param {[]string} requiredFields - * @return {Boolean} true if module subsection was initialized with success - */ -function initSubSection(moduleParams, subSection, ...requiredFields) { - const weboSectionConf = moduleParams[subSection] || {enabled: false}; + this.#fetchContextualProfile(weboCtxConf, (data) => { + logMessage('fetchContextualProfile on getBidRequestData is done'); - if (weboSectionConf.enabled === false) { - delete moduleParams[subSection]; + this.#setWeboContextualProfile(data); + }, () => { + this.#handleBidRequestData(reqBidsConfigObj, moduleParams); - return false; + onDone(); + }); } - requiredFields ||= []; + /** function that provides ad server targeting data to RTD-core + * @method + * @param {string[]} adUnitsCodes + * @param {Object} moduleConfig + * @param {?ModuleParams} moduleConfig.params + * @returns {Object} target data + */ + getTargetingData(adUnitsCodes, moduleConfig) { + /** @type {ModuleParams} */ + const moduleParams = moduleConfig?.params || {}; + + const profileHandlers = this.#buildProfileHandlers(moduleParams); + + if (isEmpty(profileHandlers)) { + logMessage('no data to set targeting'); + return {}; + } - try { - normalizeConf(moduleParams, weboSectionConf); + try { + return adUnitsCodes.reduce((data, adUnitCode) => { + data[adUnitCode] = profileHandlers.reduce((targeting, ph) => { + // logMessage(`check if should set targeting for adunit '${adUnitCode}'`); + const [data, metadata] = this.#copyDataAndMetadata(ph); + if (ph.setTargeting(adUnitCode, data, metadata)) { + // logMessage(`set targeting for adunit '${adUnitCode}', source '${metadata.source}'`); + + mergeDeep(targeting, data); + } - requiredFields.forEach(field => { - if (!weboSectionConf[field]) { - throw `missing required field "{field}" on {section}`; - } - }); - } catch (e) { - logError(`unable to initialize: error on ${subSection} configuration: ${e}`); - return false; - } + return targeting; + }, {}); - logMessage(`weborama ${subSection} initialized with success`); + return data; + }, {}); + } catch (e) { + logError(`unable to format weborama rtd targeting data:`, e); - return true; -} + return {}; + } + } -/** @type {Object} */ -const globalDefaults = { - setPrebidTargeting: true, - sendToBidders: true, - onData: () => { - /* do nothing */ }, -}; + /** Initialize subsection module + * @method + * @private + * @param {ModuleParams} moduleParams + * @param {string} subSection subsection name to initialize + * @param {string[]} requiredFields + * @return {boolean} true if module subsection was initialized with success + */ + #initSubSection(moduleParams, subSection, ...requiredFields) { + /** @type {CommonConf} */ + const weboSectionConf = moduleParams[subSection] || { enabled: false }; + + if (weboSectionConf.enabled === false) { + delete moduleParams[subSection]; -/** normalize submodule configuration - * @param {ModuleParams} moduleParams - * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams - * @return {void} - */ -function normalizeConf(moduleParams, submoduleParams) { - // handle defaults - Object.entries(globalDefaults).forEach(([propertyName, globalDefaultValue]) => { - if (!submoduleParams.hasOwnProperty(propertyName)) { - const hasModuleParam = moduleParams.hasOwnProperty(propertyName); - submoduleParams[propertyName] = (hasModuleParam) ? moduleParams[propertyName] : globalDefaultValue; + return false; } - }) - // handle setPrebidTargeting - coerceSetPrebidTargeting(submoduleParams) + try { + this.#normalizeConf(moduleParams, weboSectionConf); - // handle sendToBidders - coerceSendToBidders(submoduleParams) + requiredFields.forEach(field => { + if (!(field in weboSectionConf)) { + throw `missing required field '${field}''`; + } + }); + } catch (e) { + logError(`unable to initialize: error on ${subSection} configuration:`, e); + return false; + } + + logMessage(`weborama ${subSection} initialized with success`); - if (!isFn(submoduleParams.onData)) { - throw 'onData parameter should be a callback'; + return true; } - submoduleParams.defaultProfile = submoduleParams.defaultProfile || {}; + /** normalize submodule configuration + * @method + * @private + * @param {ModuleParams} moduleParams + * @param {CommonConf} submoduleParams + * @return {void} + * @throws will throw an error in case of invalid configuration + */ + // eslint-disable-next-line no-dupe-class-members + #normalizeConf(moduleParams, submoduleParams) { + submoduleParams.defaultProfile = submoduleParams.defaultProfile || {}; - if (!isValidProfile(submoduleParams.defaultProfile)) { - throw 'defaultProfile is not valid'; - } -} + const { setPrebidTargeting, sendToBidders, onData } = moduleParams; -/** coerce set prebid targeting to function - * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams - * @return {void} - */ -function coerceSetPrebidTargeting(submoduleParams) { - const setPrebidTargeting = submoduleParams.setPrebidTargeting; + submoduleParams.setPrebidTargeting ??= setPrebidTargeting; + submoduleParams.sendToBidders ??= sendToBidders; + submoduleParams.onData ??= onData; - if (isFn(setPrebidTargeting)) { - return - } + // handle setPrebidTargeting + this.#coerceSetPrebidTargeting(submoduleParams); - if (isBoolean(setPrebidTargeting)) { - const shouldSetPrebidTargeting = setPrebidTargeting; + // handle sendToBidders + this.#coerceSendToBidders(submoduleParams); - submoduleParams.setPrebidTargeting = () => shouldSetPrebidTargeting; + if (!isFn(submoduleParams.onData)) { + throw 'onData parameter should be a callback'; + } - return + if (!isValidProfile(submoduleParams.defaultProfile)) { + throw 'defaultProfile is not valid'; + } } - if (isStr(setPrebidTargeting)) { - const allowedAdUnitCode = setPrebidTargeting; + /** coerce setPrebidTargeting to a callback + * @method + * @private + * @param {CommonConf} submoduleParams + * @return {void} + * @throws will throw an error in case of invalid configuration + */ + // eslint-disable-next-line no-dupe-class-members + #coerceSetPrebidTargeting(submoduleParams) { + try { + submoduleParams.setPrebidTargeting = this.#wrapValidatorCallback(submoduleParams.setPrebidTargeting); + } catch (e) { + throw `invalid setPrebidTargeting: ${e}`; + } + } - submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCode == adUnitCode; + /** coerce sendToBidders to a callback + * @method + * @private + * @param {CommonConf} submoduleParams + * @return {void} + * @throws will throw an error in case of invalid configuration + */ + // eslint-disable-next-line no-dupe-class-members + #coerceSendToBidders(submoduleParams) { + let sendToBidders = submoduleParams.sendToBidders; + + if (isPlainObject(sendToBidders)) { + const sendToBiddersMap = Object.entries(sendToBidders).reduce((map, [key, value]) => { + map[key] = this.#wrapValidatorCallback(value); + return map; + }, {}); - return - } + submoduleParams.sendToBidders = (bid, adUnitCode) => { + const bidder = bid.bidder; + if (!(bidder in sendToBiddersMap)) { + return false; + } - if (isArray(setPrebidTargeting)) { - const allowedAdUnitCodes = setPrebidTargeting; + const validatorCallback = sendToBiddersMap[bidder]; - submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCodes.includes(adUnitCode); + try { + return validatorCallback(adUnitCode); + } catch (e) { + throw `invalid sendToBidders[${bidder}]: ${e}`; + } + }; - return + return; + } + + try { + submoduleParams.sendToBidders = this.#wrapValidatorCallback(submoduleParams.sendToBidders, + (bid) => bid.bidder); + } catch (e) { + throw `invalid sendToBidders: ${e}`; + } } - throw `unexpected format for setPrebidTargeting: ${typeof setPrebidTargeting}`; -} + /** + * @typedef {Object} AdUnit + * @property {Object[]} bids + */ + /** function that handles bid request data + * @method + * @private + * @param {Object} reqBidsConfigObj + * @param {AdUnit[]} reqBidsConfigObj.adUnits + * @param {Object} reqBidsConfigObj.ortb2Fragments + * @param {Object} reqBidsConfigObj.ortb2Fragments.bidder + * @param {ModuleParams} moduleParams + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #handleBidRequestData(reqBidsConfigObj, moduleParams) { + const profileHandlers = this.#buildProfileHandlers(moduleParams); + + if (isEmpty(profileHandlers)) { + logMessage('no data to send to bidders'); + return; + } -/** coerce send to bidders to function - * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams - * @return {void} - */ -function coerceSendToBidders(submoduleParams) { - const sendToBidders = submoduleParams.sendToBidders; + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + try { + adUnits.forEach( + adUnit => adUnit.bids?.forEach( + bid => profileHandlers.forEach(ph => { + // logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); + + const [data, metadata] = this.#copyDataAndMetadata(ph); + if (ph.sendToBidders(bid, adUnit.code, data, metadata)) { + // logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`); + + this.#handleBid(reqBidsConfigObj, bid, data, ph.metadata); + } + }) + ) + ); + } catch (e) { + logError('unable to send data to bidders:', e); + } - if (isFn(sendToBidders)) { - return + profileHandlers.forEach(ph => { + try { + const [data, metadata] = this.#copyDataAndMetadata(ph); + ph.onData(data, metadata); + } catch (e) { + logError(`error while execute onData callback with ${ph.metadata.source}-based data:`, e); + } + }); } - if (isBoolean(sendToBidders)) { - const shouldSendToBidders = sendToBidders; + /** onSuccess callback type + * @callback successCallback + * @param {?Object} data + * @returns {void} + */ + + /** onDone callback type + * @callback doneCallback + * @returns {void} + */ + + /** Fetch Bigsea Contextual Profile + * @method + * @private + * @param {WeboCtxConf} weboCtxConf + * @param {successCallback} onSuccess callback + * @param {doneCallback} onDone callback + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #fetchContextualProfile(weboCtxConf, onSuccess, onDone) { + const token = weboCtxConf.token; + const baseURLProfileAPI = weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; + + let path = '/profile'; + let queryString = ''; + queryString = tryAppendQueryString(queryString, 'token', token); + + if (weboCtxConf.assetID) { + path = '/document-profile'; + + let assetID = weboCtxConf.assetID; + if (isFn(assetID)) { + try { + assetID = weboCtxConf.assetID(); + } catch (e) { + logError('unexpected error while fetching asset id from callback', e); - submoduleParams.sendToBidders = () => shouldSendToBidders; + onDone(); - return - } + return; + } + } - if (isStr(sendToBidders)) { - const allowedBidder = sendToBidders; + if (!assetID) { + logError('missing asset id'); - submoduleParams.sendToBidders = (bid) => allowedBidder == bid.bidder; + onDone(); - return - } + return; + } - if (isArray(sendToBidders)) { - const allowedBidders = sendToBidders; + queryString = tryAppendQueryString(queryString, 'assetId', assetID); + } - submoduleParams.sendToBidders = (bid) => allowedBidders.includes(bid.bidder); + const targetURL = weboCtxConf.targetURL || document.URL; + queryString = tryAppendQueryString(queryString, 'url', targetURL); - return - } + const urlProfileAPI = `https://${baseURLProfileAPI}/api${path}?${queryString}`; - if (isPlainObject(sendToBidders)) { - const sendToBiddersMap = sendToBidders; - submoduleParams.sendToBidders = (bid, adUnitCode) => { - const bidder = bid.bidder; - if (!sendToBiddersMap.hasOwnProperty(bidder)) { - return false + const success = (response, req) => { + if (req.status === 200) { + const data = JSON.parse(response); + onSuccess(data); + } else { + throw `unexpected http status response ${req.status} with response ${response}`; } - const value = sendToBiddersMap[bidder]; + onDone(); + }; - if (isBoolean(value)) { - return value - } + const error = (e, req) => { + logError(`unable to get weborama data`, e, req); - if (isStr(value)) { - return value == adUnitCode - } + onDone(); + }; - if (isArray(value)) { - return value.includes(adUnitCode) - } + const callback = { + success, + error, + }; - throw `unexpected format for sendToBidders[${bidder}]: ${typeof value}`; + const options = { + method: 'GET', + withCredentials: false, }; - return + ajax(urlProfileAPI, callback, null, options); } - throw `unexpected format for sendToBidders: ${typeof sendToBidders}`; -} -/** - * check if profile is valid - * @param {*} profile - * @returns {Boolean} - */ -function isValidProfile(profile) { - if (!isPlainObject(profile)) { - return false; + /** set bigsea contextual profile on module state + * @method + * @private + * @param {?Object} data + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #setWeboContextualProfile(data) { + if (data && isPlainObject(data) && isValidProfile(data) && !isEmpty(data)) { + this.#components.WeboCtx.data = data; + } } - const keys = Object.keys(profile); + /** function that provides data handlers based on the configuration + * @method + * @private + * @param {ModuleParams} moduleParams + * @returns {ProfileHandler[]} + */ + // eslint-disable-next-line no-dupe-class-members + #buildProfileHandlers(moduleParams) { + const steps = [{ + component: this.#components.WeboCtx, + conf: moduleParams?.weboCtxConf, + }, { + component: this.#components.WeboUserData, + conf: moduleParams?.weboUserDataConf, + }, { + component: this.#components.SfbxLiteData, + conf: moduleParams?.sfbxLiteDataConf, + }]; + + return steps.filter(step => step.component.initialized).reduce((ph, { component, conf }) => { + const user = component.user; + const source = component.source; + const callback = component.callbackBuilder(component /* equivalent to this */); + const profileHandler = this.#buildProfileHandler(conf, callback, user, source); + if (profileHandler) { + ph.push(profileHandler); + } else { + logMessage(`skip ${source} profile: no data`); + } - for (var i in keys) { - const key = keys[i]; - const value = profile[key]; - if (!isArray(value)) { - return false; + return ph; + }, []); + } + + /** + * @typedef {Object} ProfileHandler + * @property {Profile} data + * @property {dataCallbackMetadata} metadata + * @property {setPrebidTargetingCallback} setTargeting + * @property {sendToBiddersCallback} sendToBidders + * @property {dataCallback} onData + */ + + /** + * @callback buildProfileHandlerCallbackBuilder + * @param {Component} component + * @returns {buildProfileHandlerCallback} + */ + + /** + * @callback buildProfileHandlerCallback + * @param {CommonConf} dataConf + * @returns {[Profile,boolean]} profile + is default flag + */ + + /** + * return specific profile handler + * @method + * @private + * @param {CommonConf} dataConf + * @param {buildProfileHandlerCallback} callback + * @param {boolean} user + * @param {string} source + * @returns {ProfileHandler} + */ + // eslint-disable-next-line no-dupe-class-members + #buildProfileHandler(dataConf, callback, user, source) { + if (!dataConf) { + return; } - for (var j in value) { - const elem = value[j] - if (!isStr(elem)) { - return false; - } + const [data, isDefault] = callback(dataConf); + if (isEmpty(data)) { + return; + } + + return { + data: data, + metadata: { + user: user, + source: source, + isDefault: !!isDefault, + }, + setTargeting: dataConf.setPrebidTargeting, + sendToBidders: dataConf.sendToBidders, + onData: dataConf.onData, + }; + } + /** handle individual bid + * @method + * @private + * @param {Object} reqBidsConfigObj + * @param {Object} reqBidsConfigObj.ortb2Fragments + * @param {Object} reqBidsConfigObj.ortb2Fragments.bidder + * @param {Object} bid + * @param {string} bid.bidder + * @param {Profile} profile + * @param {dataCallbackMetadata} metadata + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #handleBid(reqBidsConfigObj, bid, profile, metadata) { + this.#handleBidViaORTB2(reqBidsConfigObj, bid.bidder, profile, metadata); + + /** @type {Object.} */ + const bidderAliasRegistry = adapterManager.aliasRegistry || {}; + + /** @type {string} */ + const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; + + switch (bidder) { + case 'appnexus': + this.#handleAppnexusBid(bid, profile); + break; + case 'pubmatic': + this.#handlePubmaticBid(bid, profile); + break; + case 'smartadserver': + this.#handleSmartadserverBid(bid, profile); + break; + case 'rubicon': + this.#handleRubiconBid(bid, profile, metadata); + break; } } - return true; -} + /** function that handles bid request data + * @method + * @private + * @param {ProfileHandler} ph profile handler + * @returns {[Profile,dataCallbackMetadata]} deeply copy data + metadata + */ + // eslint-disable-next-line no-dupe-class-members + #copyDataAndMetadata(ph) { + return [deepClone(ph.data), deepClone(ph.metadata)]; + } -/** function that provides ad server targeting data to RTD-core - * @param {Array} adUnitsCodes - * @param {Object} moduleConfig - * @returns {Object} target data - */ -function getTargetingData(adUnitsCodes, moduleConfig) { - const moduleParams = moduleConfig?.params || {}; + /** handle appnexus/xandr bid + * @method + * @private + * @param {Object} bid + * @param {Object} bid.params + * @param {Object} bid.params.keyword + * @param {Profile} profile + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #handleAppnexusBid(bid, profile) { + const base = 'params.keywords'; + this.#assignProfileToObject(bid, base, profile); + } - const profileHandlers = buildProfileHandlers(moduleParams); + /** handle pubmatic bid + * @method + * @private + * @param {Object} bid + * @param {Object} bid.params + * @param {string} bid.params.dctr + * @param {Profile} profile + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #handlePubmaticBid(bid, profile) { + const sep = '|'; + const subsep = ','; + + bid.params ||= {}; + + const data = bid.params.dctr || ''; + const target = new Set(data.split(sep).filter((x) => x.length > 0)); + + Object.entries(profile).forEach(([key, values]) => { + const value = values.join(subsep); + const keyword = `${key}=${value}`; + target.add(keyword); + }); - if (isEmpty(profileHandlers)) { - logMessage('no data to set targeting'); - return {}; + bid.params.dctr = Array.from(target).join(sep); } - try { - const td = adUnitsCodes.reduce((data, adUnitCode) => { - data[adUnitCode] = profileHandlers.reduce((targeting, ph) => { - // logMessage(`check if should set targeting for adunit '${adUnitCode}'`); - const cph = copyProfileHandler(ph); - if (ph.setTargeting(adUnitCode, cph.data, cph.metadata)) { - // logMessage(`set targeting for adunit '${adUnitCode}', source '${ph.metadata.source}'`); - - mergeDeep(targeting, cph.data); - } - return targeting; - }, {}); - return data; - }, {}); + /** handle smartadserver bid + * @method + * @private + * @param {Object} bid + * @param {Object} bid.params + * @param {string} bid.params.target + * @param {Profile} profile + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #handleSmartadserverBid(bid, profile) { + const sep = ';'; + + bid.params ||= {}; + + const data = bid.params.target || ''; + const target = new Set(data.split(sep).filter((x) => x.length > 0)); + + Object.entries(profile).forEach(([key, values]) => { + values.forEach(value => { + const keyword = `${key}=${value}`; + target.add(keyword); + }) + }); - return td; - } catch (e) { - logError('unable to format weborama rtd targeting data', e); - return {}; + bid.params.target = Array.from(target).join(sep); } -} -/** function that provides data handlers based on the configuration - * @param {ModuleParams} moduleParams - * @returns {Array} handlers - */ -function buildProfileHandlers(moduleParams) { - const profileHandlers = []; - - if (_weboCtxInitialized && moduleParams?.weboCtxConf) { - const weboCtxConf = moduleParams.weboCtxConf; - const [data, isDefault] = getContextualProfile(weboCtxConf); - if (!isEmpty(data)) { - profileHandlers.push({ - data: data, - metadata: { - user: false, - source: 'contextual', - isDefault: !!isDefault, - }, - setTargeting: weboCtxConf.setPrebidTargeting, - sendToBidders: weboCtxConf.sendToBidders, - onData: weboCtxConf.onData, - }) + /** handle rubicon bid + * @method + * @private + * @param {Object} bid + * @param {string} bid.bidder + * @param {Profile} profile + * @param {dataCallbackMetadata} metadata + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #handleRubiconBid(bid, profile, metadata) { + if (isBoolean(metadata.user)) { + const section = metadata.user ? 'visitor' : 'inventory'; + const base = `params.${section}`; + this.#assignProfileToObject(bid, base, profile); } else { - logMessage('skip contextual profile: no data'); + logMessage(`SKIP bidder '${bid.bidder}', data from '${metadata.source}' is not defined as user or site-centric`); } } - if (_weboUserDataInitialized && moduleParams?.weboUserDataConf) { - const weboUserDataConf = moduleParams.weboUserDataConf; - const [data, isDefault] = getWeboUserDataProfile(weboUserDataConf); - if (!isEmpty(data)) { - profileHandlers.push({ - data: data, - metadata: { - user: true, - source: 'wam', - isDefault: !!isDefault, - }, - setTargeting: weboUserDataConf.setPrebidTargeting, - sendToBidders: weboUserDataConf.sendToBidders, - onData: weboUserDataConf.onData, - }) + /** handle generic bid via ortb2 arbitrary data + * @method + * @private + * @param {Object} reqBidsConfigObj + * @param {Object} reqBidsConfigObj.ortb2Fragments + * @param {Object} reqBidsConfigObj.ortb2Fragments.bidder + * @param {string} bidder + * @param {Profile} profile + * @param {dataCallbackMetadata} metadata + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #handleBidViaORTB2(reqBidsConfigObj, bidder, profile, metadata) { + if (isBoolean(metadata.user)) { + logMessage(`bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); + const section = metadata.user ? 'user' : 'site'; + const base = `${bidder}.${section}.ext.data`; + + this.#assignProfileToObject(reqBidsConfigObj.ortb2Fragments?.bidder, base, profile); } else { - logMessage('skip wam profile: no data'); + logMessage(`SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric`); } } - if (_sfbxLiteDataInitialized && moduleParams?.sfbxLiteDataConf) { - const sfbxLiteDataConf = moduleParams.sfbxLiteDataConf; - const [data, isDefault] = getSfbxLiteDataProfile(sfbxLiteDataConf); - if (!isEmpty(data)) { - profileHandlers.push({ - data: data, - metadata: { - user: false, - source: 'lite', - isDefault: !!isDefault, - }, - setTargeting: sfbxLiteDataConf.setPrebidTargeting, - sendToBidders: sfbxLiteDataConf.sendToBidders, - onData: sfbxLiteDataConf.onData, - }) - } else { - logMessage('skip sfbx lite profile: no data'); - } + /** + * assign profile to object + * @method + * @private + * @param {Object} destination + * @param {string} base + * @param {Profile} profile + * @returns {void} + */ + // eslint-disable-next-line no-dupe-class-members + #assignProfileToObject(destination, base, profile) { + Object.entries(profile).forEach(([key, values]) => { + const path = `${base}.${key}`; + deepSetValue(destination, path, values); + }) } - return profileHandlers; + /** + * @callback validatorCallback + * @param {string} target + * @returns {boolean} + */ + + /** + * @callback coerceCallback + * @param {*} input + * @returns {*} + */ + + /** + * wrap value into validator + * @method + * @private + * @param {*} value + * @param {coerceCallback} coerce + * @returns {validatorCallback} + * @throws will throw an error in case of unsupported type + */ + // eslint-disable-next-line no-dupe-class-members + #wrapValidatorCallback(value, coerce = (x) => x) { + if (isFn(value)) { + return value; + } + + if (isBoolean(value)) { + return (_) => value; + } + + if (isStr(value)) { + return (target) => { + return value == coerce(target); + }; + } + + if (isArray(value)) { + return (target) => { + return value.includes(coerce(target)); + }; + } + + throw `unexpected format: ${typeof value} (expects function, boolean, string or array)`; + } } -/** return contextual profile - * @param {WeboCtxConf} weboCtxConf - * @returns {Array} contextual profile + isDefault boolean flag +/** + * check if profile is valid + * @param {*} profile + * @returns {boolean} */ -function getContextualProfile(weboCtxConf) { - if (_weboContextualProfile) { - return [_weboContextualProfile, false]; +export function isValidProfile(profile) { + if (!isPlainObject(profile)) { + return false; } - const defaultContextualProfile = weboCtxConf.defaultProfile || {}; + return Object.values(profile).every((field) => isArray(field) && field.every(isStr)); +} + +/** + * bind callback with component + * @param {Component} component + * @returns {buildProfileHandlerCallback} + */ +function getContextualProfile(component /* equivalent to this */) { + /** return contextual profile + * @param {WeboCtxConf} weboCtxConf + * @returns {[Profile,boolean]} contextual profile + isDefault boolean flag + */ + return function (weboCtxConf) { + if (component.data) { + return [component.data, false]; + } - return [defaultContextualProfile, true]; + const defaultContextualProfile = weboCtxConf.defaultProfile || {}; + + return [defaultContextualProfile, true]; + } } -/** return weboUserData profile - * @param {WeboUserDataConf} weboUserDataConf - * @returns {Array} weboUserData profile + isDefault boolean flag +/** + * bind callback with component + * @param {Component} component + * @returns {buildProfileHandlerCallback} */ -function getWeboUserDataProfile(weboUserDataConf) { - return getDataFromLocalStorage(weboUserDataConf, - () => _weboUserDataUserProfile, - (data) => _weboUserDataUserProfile = data, - DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY, - LOCAL_STORAGE_USER_TARGETING_SECTION, - 'wam'); +function getWeboUserDataProfile(component /* equivalent to this */) { + /** return weboUserData profile + * @param {WeboUserDataConf} weboUserDataConf + * @returns {[Profile,boolean]} weboUserData profile + isDefault boolean flag + */ + return function (weboUserDataConf) { + return getDataFromLocalStorage(weboUserDataConf, + () => component.data, + (data) => component.data = data, + DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY, + LOCAL_STORAGE_USER_TARGETING_SECTION, + WEBO_USER_DATA_SOURCE_LABEL); + } } -/** return weboUserData profile - * @param {SfbxLiteDataConf} sfbxLiteDataConf - * @returns {Array} sfbxLiteData profile + isDefault boolean flag +/** + * bind callback with component + * @param {Component} component + * @returns {buildProfileHandlerCallback} */ -function getSfbxLiteDataProfile(sfbxLiteDataConf) { - return getDataFromLocalStorage(sfbxLiteDataConf, - () => _sfbxLiteDataProfile, - (data) => _sfbxLiteDataProfile = data, - DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY, - LOCAL_STORAGE_LITE_TARGETING_SECTION, - 'lite'); +function getSfbxLiteDataProfile(component /* equivalent to this */) { + /** return weboUserData profile + * @param {SfbxLiteDataConf} sfbxLiteDataConf + * @returns {[Profile,boolean]} sfbxLiteData profile + isDefault boolean flag + */ + return function getSfbxLiteDataProfile(sfbxLiteDataConf) { + return getDataFromLocalStorage(sfbxLiteDataConf, + () => component.data, + (data) => component.data = data, + DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY, + LOCAL_STORAGE_LITE_TARGETING_SECTION, + SFBX_LITE_DATA_SOURCE_LABEL); + } } +/** + * @callback cacheGetCallback + * @returns {Profile} + */ +/** + * @callback cacheSetCallback + * @param {Profile} profile + * @returns {void} + */ + /** return generic webo data profile * @param {WeboUserDataConf|SfbxLiteDataConf} weboDataConf * @param {cacheGetCallback} cacheGet * @param {cacheSetCallback} cacheSet - * @param {String} defaultLocalStorageProfileKey - * @param {String} targetingSection - * @param {String} source - * @returns {Array} webo (user|lite) data profile + isDefault boolean flag + * @param {string} defaultLocalStorageProfileKey + * @param {string} targetingSection + * @param {string} source + * @returns {[Profile,boolean]} webo (user|lite) data profile + isDefault boolean flag */ function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalStorageProfileKey, targetingSection, source) { const defaultProfile = weboDataConf.defaultProfile || {}; - if (storage.localStorageIsEnabled() && !cacheGet()) { + if (storage.hasLocalStorage() && storage.localStorageIsEnabled() && !cacheGet()) { const localStorageProfileKey = weboDataConf.localStorageProfileKey || defaultLocalStorageProfileKey; const entry = storage.getDataFromLocalStorage(localStorageProfileKey); if (entry) { const data = JSON.parse(entry); - if (data && isPlainObject(data) && data.hasOwnProperty(targetingSection)) { + if (data && isPlainObject(data) && targetingSection in data) { + /** @type {profile} */ const profile = data[targetingSection]; const valid = isValidProfile(profile); if (!valid) { logWarn(`found invalid ${source} profile on local storage key ${localStorageProfileKey}, section ${targetingSection}`); + + return; } - if (valid && !isEmpty(data)) { + if (!isEmpty(data)) { cacheSet(profile); } } @@ -566,328 +1020,31 @@ function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalS return [defaultProfile, true]; } - -/** function that will allow RTD sub-modules to modify the AdUnit object for each auction - * @param {Object} reqBidsConfigObj - * @param {doneCallback} onDone - * @param {Object} moduleConfig - * @returns {void} - */ -export function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig) { - const moduleParams = moduleConfig?.params || {}; - - if (!_weboCtxInitialized) { - handleBidRequestData(reqBidsConfigObj, moduleParams); - - onDone(); - - return; - } - - const weboCtxConf = moduleParams.weboCtxConf || {}; - - fetchContextualProfile(weboCtxConf, (data) => { - logMessage('fetchContextualProfile on getBidRequestData is done'); - - setWeboContextualProfile(data); - }, () => { - handleBidRequestData(reqBidsConfigObj, moduleParams); - - onDone(); - }); -} - -/** function that handles bid request data - * @param {Object} reqBids - * @param {ModuleParams} moduleParams - * @returns {void} - */ -function handleBidRequestData(reqBids, moduleParams) { - const profileHandlers = buildProfileHandlers(moduleParams); - - if (isEmpty(profileHandlers)) { - logMessage('no data to send to bidders'); - return; - } - - const adUnits = reqBids.adUnits || getGlobal().adUnits; - - try { - adUnits.filter( - adUnit => adUnit.hasOwnProperty('bids') - ).forEach( - adUnit => adUnit.bids.forEach( - bid => profileHandlers.forEach(ph => { - // logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); - - const cph = copyProfileHandler(ph); - if (ph.sendToBidders(bid, adUnit.code, cph.data, cph.metadata)) { - // logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`); - - handleBid(reqBids, bid, cph.data, ph.metadata); - } - }) - ) - ); - } catch (e) { - logError('unable to send data to bidders:', e); - } - - profileHandlers.forEach(ph => { - try { - const cph = copyProfileHandler(ph); - ph.onData(cph.data, cph.metadata); - } catch (e) { - logError(`error while executure onData callback with ${ph.metadata.source}-based data:`, e); - } - }); -} -/** function that handles bid request data - * @param {Object} ph profile handler - *@returns {Object} of deeply copy data and metadata - */ -function copyProfileHandler(ph) { - return { - data: deepClone(ph.data), - metadata: deepClone(ph.metadata), - }; -} - -/** @type {string} */ -const APPNEXUS = 'appnexus'; - -/** @type {string} */ -const PUBMATIC = 'pubmatic'; - -/** @type {string} */ -const RUBICON = 'rubicon'; - -/** @type {string} */ -const SMARTADSERVER = 'smartadserver'; - -/** @type {Object} */ -const bidderAliasRegistry = adapterManager.aliasRegistry || {}; - -/** handle individual bid - * @param reqBids - * @param {Object} bid - * @param {Object} profile - * @param {Object} metadata - * @returns {void} - */ -function handleBid(reqBids, bid, profile, metadata) { - const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; - - switch (bidder) { - // TODO: these special cases should not be necessary - all adapters should look into FPD, not just their params - case APPNEXUS: - handleAppnexusBid(bid, profile); - - break; - - case PUBMATIC: - handlePubmaticBid(bid, profile); - - break; - - case SMARTADSERVER: - handleSmartadserverBid(bid, profile); - - break; - case RUBICON: - handleRubiconBid(bid, profile, metadata); - - break; - default: - handleBidViaORTB2(reqBids, bid, profile, metadata); - } -} - -/** handle appnexus/xandr bid - * @param {Object} bid - * @param {Object} profile - * @returns {void} - */ -function handleAppnexusBid(bid, profile) { - const base = 'params.keywords'; - assignProfileToObject(bid, base, profile); -} - -/** handle pubmatic bid - * @param {Object} bid - * @param {Object} profile - * @returns {void} - */ -function handlePubmaticBid(bid, profile) { - const sep = '|'; - const subsep = ','; - const target = []; - - bid.params ||= {}; - - const data = bid.params.dctr; - if (data) { - data.split(sep).forEach(t => target.push(t)); - } - - Object.keys(profile).forEach(key => { - const value = profile[key].join(subsep); - const keyword = `${key}=${value}`; - if (target.indexOf(keyword) === -1) { - target.push(keyword); - } - }); - - bid.params.dctr = target.join(sep); -} - -/** handle smartadserver bid - * @param {Object} bid - * @param {Object} profile - * @returns {void} - */ -function handleSmartadserverBid(bid, profile) { - const sep = ';'; - const target = []; - - bid.params ||= {}; - - const data = bid.params.target; - if (data) { - data.split(sep).forEach(t => target.push(t)); - } - - Object.keys(profile).forEach(key => { - profile[key].forEach(value => { - const keyword = `${key}=${value}`; - if (target.indexOf(keyword) === -1) { - target.push(keyword); - } - }); - }); - - bid.params.target = target.join(sep); -} - -/** handle rubicon bid - * @param {Object} bid - * @param {Object} profile - * @param {Object} metadata - * @returns {void} - */ -function handleRubiconBid(bid, profile, metadata) { - if (isBoolean(metadata.user)) { - const section = (metadata.user) ? 'visitor' : 'inventory'; - const base = `params.${section}`; - assignProfileToObject(bid, base, profile); - } else { - logMessage(`SKIP bidder '${bid.bidder}', data from '${metadata.source}' is not defined as user or site-centric`); - } -} - -/** handle generic bid via ortb2 arbitrary data - * @param reqBids - * @param {Object} bid - * @param {Object} profile - * @param {Object} metadata - * @returns {void} - */ -function handleBidViaORTB2(reqBids, bid, profile, metadata) { - if (isBoolean(metadata.user)) { - logMessage(`bidder '${bid.bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); - const section = ((metadata.user) ? 'user' : 'site'); - const base = `${bid.bidder}.${section}.ext.data`; - - assignProfileToObject(reqBids.ortb2Fragments?.bidder, base, profile); - } else { - logMessage(`SKIP unsupported bidder '${bid.bidder}', data from '${metadata.source}' is not defined as user or site-centric`); - } -} - -/** - * assign profile to object - * @param {Object} destination - * @param {string} base - * @param {Object} profile - * @returns {void} - */ -function assignProfileToObject(destination, base, profile) { - Object.keys(profile).forEach(key => { - const path = `${base}.${key}`; - deepSetValue(destination, path, profile[key]) - }) -} - -/** set bigsea contextual profile on module state - * @param {null|Object} data - * @returns {void} - */ -export function setWeboContextualProfile(data) { - if (data && isPlainObject(data) && isValidProfile(data) && !isEmpty(data)) { - _weboContextualProfile = data; - } -} - -/** onSuccess callback type - * @callback successCallback - * @param {null|Object} data - * @returns {void} - */ - -/** onDone callback type - * @callback doneCallback - * @returns {void} - */ - -/** Fetch Bigsea Contextual Profile - * @param {WeboCtxConf} weboCtxConf - * @param {successCallback} onSuccess callback - * @param {doneCallback} onDone callback - * @returns {void} - */ -function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { - const targetURL = weboCtxConf.targetURL || document.URL; - const token = weboCtxConf.token; - const baseURLProfileAPI = weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; - - let queryString = ''; - queryString = tryAppendQueryString(queryString, 'token', token); - queryString = tryAppendQueryString(queryString, 'url', targetURL); - - const urlProfileAPI = `https://${baseURLProfileAPI}/api/profile?${queryString}`; - - ajax(urlProfileAPI, { - success: (response, req) => { - if (req.status === 200) { - try { - const data = JSON.parse(response); - onSuccess(data); - onDone(); - } catch (e) { - onDone(); - logError('unable to parse weborama data', e); - throw e; - } - } else if (req.status === 204) { - onDone(); - } - }, - error: () => { - onDone(); - logError('unable to get weborama data'); - } +/** @type {Components} */ +const components = { + WeboCtx: { + initialized: false, + data: null, + user: false, + source: WEBO_CTX_SOURCE_LABEL, + callbackBuilder: getContextualProfile, + }, + WeboUserData: { + initialized: false, + data: null, + user: true, + source: WEBO_USER_DATA_SOURCE_LABEL, + callbackBuilder: getWeboUserDataProfile, + }, + SfbxLiteData: { + initialized: false, + data: null, + user: false, + source: SFBX_LITE_DATA_SOURCE_LABEL, + callbackBuilder: getSfbxLiteDataProfile, }, - null, { - method: 'GET', - withCredentials: false, - }); -} - -export const weboramaSubmodule = { - name: SUBMODULE_NAME, - init: init, - getTargetingData: getTargetingData, - getBidRequestData: getBidRequestData, }; +export const weboramaSubmodule = new WeboramaRtdProvider(components); + submodule(MODULE_NAME, weboramaSubmodule); diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index 88ec907c9a1..f86351fe214 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -109,6 +109,7 @@ On this section we will explain the `params.weboCtxConf` subconfiguration: | :------------ | :------------ | :------------ |:------------ | | token | String | Security Token provided by Weborama, unique per client | Mandatory | | targetURL | String | Url to be profiled in the contextual api | Optional. Defaults to `document.URL` | +| assetID | Function or String | if provided, we will call the document-profile api using this asset id. |Optional| | setPrebidTargeting|Various|If true, will use the contextual profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or `true`.| | sendToBidders|Various|If true, will send the contextual profile to all bidders. If an array, will specify the bidders to send data| Optional. Default is `params.sendToBidders` (if any) or `true`.| | defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | @@ -383,6 +384,37 @@ pbjs.que.push(function () { }); ``` +An alternative version, using asset id instead of target url on contextual, can be found here: + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { + token: "<>", // mandatory + assetID: "datasource:docId", // can be a callback to be executed in runtime and returns the identifier + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + defaultProfile: { // optional, used if nothing is found + webo_ctx: [ ... ], // contextual segments + webo_ds: [ ...], // data science segments + }, + enabled: true, + }, + ... + }); +}); +``` + Imagine we need to configure the following options using the previous example, we can write the configuration like the one below. ||contextual|wam|lite| diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index 51d9bd30b84..40677f90064 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -24,12 +24,13 @@ const SUPPORTED_USER_ID_SOURCES = [ 'adserver.org', 'adtelligent.com', 'akamai.com', - 'amxrtb.com', + 'amxdt.net', 'audigent.com', 'britepool.com', 'criteo.com', 'crwdcntrl.net', 'deepintent.com', + 'epsilon.com', 'hcn.health', 'id5-sync.com', 'idx.lat', @@ -46,7 +47,6 @@ const SUPPORTED_USER_ID_SOURCES = [ 'parrable.com', 'pubcid.org', 'quantcast.com', - 'quantcast.com', 'tapad.com', 'uidapi.com', 'verizonmedia.com', @@ -279,6 +279,11 @@ function generateOpenRtbObject(bidderRequest, bid) { outBoundBidRequest.site.id = bid.params.dcn; }; + if (bidderRequest.ortb2?.regs?.gpp) { + outBoundBidRequest.regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + outBoundBidRequest.regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid + }; + if (bidderRequest.ortb2) { outBoundBidRequest = appendFirstPartyData(outBoundBidRequest, bid); }; diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index af465453c22..f73b4369869 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -1,5 +1,6 @@ -import { formatQS, deepAccess, triggerPixel } from '../src/utils.js'; +import { formatQS, deepAccess, triggerPixel, isArray, isNumber } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' const BIDDER_CODE = 'yandex'; const BIDDER_URL = 'https://bs.yandex.ru/metadsp'; @@ -10,9 +11,19 @@ const SSP_ID = 10500; export const spec = { code: BIDDER_CODE, aliases: ['ya'], // short code + supportedMediaTypes: [ BANNER ], isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.pageId && bid.params.impId); + const { params } = bid; + if (!params) { + return false; + } + const { pageId, impId } = extractPlacementIds(params); + if (!(pageId && impId)) { + return false; + } + const sizes = bid.mediaTypes?.banner?.sizes; + return isArray(sizes) && isArray(sizes[0]) && isNumber(sizes[0][0]) && isNumber(sizes[0][1]); }, buildRequests: function(validBidRequests, bidderRequest) { @@ -29,9 +40,19 @@ export const spec = { page = bidderRequest.refererInfo.page; } + let timeout = null; + if (bidderRequest) { + timeout = bidderRequest.timeout; + } + return validBidRequests.map((bidRequest) => { const { params } = bidRequest; - const { pageId, impId, targetRef, withCredentials = true } = params; + const { targetRef, withCredentials = true } = params; + const sizes = bidRequest.mediaTypes.banner.sizes; + const size = sizes[0]; + const [ w, h ] = size; + + const { pageId, impId } = extractPlacementIds(params); const queryParams = { 'imp-id': impId, @@ -43,24 +64,23 @@ export const spec = { queryParams['tcf-consent'] = consentString; } - const floorInfo = bidRequest.getFloor ? bidRequest.getFloor({ - currency: DEFAULT_CURRENCY - }) : {}; - const bidfloor = floorInfo.floor; - const bidfloorcur = floorInfo.currency; - const imp = { id: impId, - bidfloor, - bidfloorcur, - }; - const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); - if (bannerParams) { - const [ w, h ] = bannerParams.sizes[0]; - imp.banner = { + banner: { + format: sizes, w, h, - }; + } + }; + + if (bidRequest.getFloor) { + const floorInfo = bidRequest.getFloor({ + currency: DEFAULT_CURRENCY, + size + }); + + imp.bidfloor = floorInfo.floor; + imp.bidfloorcur = floorInfo.currency; } const queryParamsString = formatQS(queryParams); @@ -74,6 +94,7 @@ export const spec = { ref_url: referrer, page_url: page, }, + tmax: timeout, }, options: { withCredentials, @@ -102,6 +123,7 @@ export const spec = { width: rtbBid.w, height: rtbBid.h, creativeId: rtbBid.adid, + nurl: rtbBid.nurl, netRevenue: true, ttl: DEFAULT_TTL, @@ -118,13 +140,57 @@ export const spec = { }, onBidWon: function (bid) { - const nurl = bid['nurl']; - + let nurl = bid['nurl']; if (!nurl) { return; } + + const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; + const curr = (bid.hasOwnProperty('originalCurrency') && bid.hasOwnProperty('originalCpm')) + ? bid.originalCurrency + : bid.currency; + + nurl = nurl + .replace(/\${AUCTION_PRICE}/, cpm) + .replace(/\${AUCTION_CURRENCY}/, curr) + ; + triggerPixel(nurl); } } +function extractPlacementIds(bidRequestParams) { + const { placementId } = bidRequestParams; + const result = { pageId: null, impId: null }; + + let pageId, impId; + if (placementId) { + /* + * Possible formats + * R-I-123456-2 + * R-123456-1 + * 123456-789 + */ + const num = placementId.lastIndexOf('-'); + if (num === -1) { + return result; + } + const num2 = placementId.lastIndexOf('-', num - 1); + pageId = placementId.slice(num2 + 1, num); + impId = placementId.slice(num + 1); + } else { + pageId = bidRequestParams.pageId; + impId = bidRequestParams.impId; + } + + if (!parseInt(pageId, 10) || !parseInt(impId, 10)) { + return result; + } + + result.pageId = pageId; + result.impId = impId; + + return result; +} + registerBidder(spec); diff --git a/modules/yandexBidAdapter.md b/modules/yandexBidAdapter.md index 7a51d7bc5fb..aee51bca249 100644 --- a/modules/yandexBidAdapter.md +++ b/modules/yandexBidAdapter.md @@ -12,10 +12,11 @@ Yandex Bidder Adapter for Prebid.js. # Parameters -| Name | Scope | Description | Example | Type | -|---------------|----------|-------------------------|-----------|-----------| -| `pageId` | required | Page ID | `123` | `Integer` | -| `impId` | required | Block ID | `1` | `Integer` | +| Name | Required? | Description | Example | Type | +|---------------|--------------------------------------------|-------------|---------|-----------| +| `placementId` | Yes | Block ID | `123-1` | `String` | +| `pageId` | No
Deprecated. Please use `placementId` | Page ID | `123` | `Integer` | +| `impId` | No
Deprecated. Please use `placementId` | Imp ID | `1` | `Integer` | # Test Parameters @@ -24,15 +25,14 @@ var adUnits = [{ code: 'banner-1', mediaTypes: { banner: { - sizes: [[240, 400]], + sizes: [[240, 400], [300, 600]], } }, bids: [{ { bidder: 'yandex', params: { - pageId: 346580, - impId: 143, + placementId: '346580-1' }, } }] diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index e5e452b4ce0..cadeb9c1300 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,17 +1,17 @@ -import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { find } from '../src/polyfill.js' -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' -import { Renderer } from '../src/Renderer.js' +import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { find } from '../src/polyfill.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -const ENDPOINT = 'https://ad.yieldlab.net' -const BIDDER_CODE = 'yieldlab' -const BID_RESPONSE_TTL_SEC = 300 -const CURRENCY_CODE = 'EUR' -const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' -const GVLID = 70 -const DIMENSION_SIGN = 'x' +const ENDPOINT = 'https://ad.yieldlab.net'; +const BIDDER_CODE = 'yieldlab'; +const BID_RESPONSE_TTL_SEC = 300; +const CURRENCY_CODE = 'EUR'; +const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event'; +const GVLID = 70; +const DIMENSION_SIGN = 'x'; export const spec = { code: BIDDER_CODE, @@ -22,11 +22,11 @@ export const spec = { * @param {object} bid * @returns {boolean} */ - isBidRequestValid: function (bid) { + isBidRequestValid(bid) { if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { - return true + return true; } - return false + return false; }, /** @@ -35,78 +35,85 @@ export const spec = { * @param [bidderRequest] * @returns {ServerRequest|ServerRequest[]} */ - buildRequests: function (validBidRequests, bidderRequest) { + buildRequests(validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const adslotIds = [] + const adslotIds = []; const adslotSizes = []; - const timestamp = Date.now() + const adslotFloors = []; + const timestamp = Date.now(); const query = { ts: timestamp, - json: true - } + json: true, + }; _each(validBidRequests, function (bid) { - adslotIds.push(bid.params.adslotId) - const sizes = extractSizes(bid) + adslotIds.push(bid.params.adslotId); + const sizes = extractSizes(bid); if (sizes.length > 0) { - adslotSizes.push(bid.params.adslotId + ':' + sizes.join('|')) + adslotSizes.push(bid.params.adslotId + ':' + sizes.join('|')); } if (bid.params.extId) { query.id = bid.params.extId; } if (bid.params.targeting) { - query.t = createTargetingString(bid.params.targeting) + query.t = createTargetingString(bid.params.targeting); } if (bid.userIdAsEids && Array.isArray(bid.userIdAsEids)) { - query.ids = createUserIdString(bid.userIdAsEids) + query.ids = createUserIdString(bid.userIdAsEids); + query.atypes = createUserIdAtypesString(bid.userIdAsEids); } if (bid.params.customParams && isPlainObject(bid.params.customParams)) { for (const prop in bid.params.customParams) { - query[prop] = bid.params.customParams[prop] + query[prop] = bid.params.customParams[prop]; } } if (bid.schain && isPlainObject(bid.schain) && Array.isArray(bid.schain.nodes)) { - query.schain = createSchainString(bid.schain) + query.schain = createSchainString(bid.schain); } - const iabContent = getContentObject(bid) + const iabContent = getContentObject(bid); if (iabContent) { - query.iab_content = createIabContentString(iabContent) + query.iab_content = createIabContentString(iabContent); } - const floor = getBidFloor(bid, sizes) + const floor = getBidFloor(bid, sizes); if (floor) { - query.floor = floor; + adslotFloors.push(bid.params.adslotId + ':' + floor); } - }) + }); if (bidderRequest) { if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { // TODO: is 'page' the right value here? - query.pubref = bidderRequest.refererInfo.page + query.pubref = bidderRequest.refererInfo.page; } if (bidderRequest.gdprConsent) { - query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + query.gdpr = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true; if (query.gdpr) { - query.consent = bidderRequest.gdprConsent.consentString + query.consent = bidderRequest.gdprConsent.consentString; } } } - const adslots = adslotIds.join(',') + const adslots = adslotIds.join(','); if (adslotSizes.length > 0) { - query.sizes = adslotSizes.join(',') + query.sizes = adslotSizes.join(','); + } + + if (adslotFloors.length > 0) { + query.floor = adslotFloors.join(','); } - const queryString = createQueryString(query) + + const queryString = createQueryString(query); return { method: 'GET', url: `${ENDPOINT}/yp/${adslots}?${queryString}`, validBidRequests: validBidRequests, - queryParams: query - } + queryParams: query, + }; }, /** @@ -115,29 +122,29 @@ export const spec = { * @param {BidRequest} originalBidRequest * @returns {Bid[]} */ - interpretResponse: function (serverResponse, originalBidRequest) { - const bidResponses = [] - const timestamp = Date.now() - const reqParams = originalBidRequest.queryParams + interpretResponse(serverResponse, originalBidRequest) { + const bidResponses = []; + const timestamp = Date.now(); + const reqParams = originalBidRequest.queryParams; originalBidRequest.validBidRequests.forEach(function (bidRequest) { if (!serverResponse.body) { - return + return; } const matchedBid = find(serverResponse.body, function (bidResponse) { - return bidRequest.params.adslotId == bidResponse.id - }) + return bidRequest.params.adslotId == bidResponse.id; + }); if (matchedBid) { - const adUnitSize = bidRequest.sizes.length === 2 && !isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0] - const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize - const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : '' - const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : '' - const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : '' - const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : '' - const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : '' - const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : '' + const adUnitSize = bidRequest.sizes.length === 2 && !isArray(bidRequest.sizes[0]) ? bidRequest.sizes : bidRequest.sizes[0]; + const adSize = bidRequest.params.adSize !== undefined ? parseSize(bidRequest.params.adSize) : (matchedBid.adsize !== undefined) ? parseSize(matchedBid.adsize) : adUnitSize; + const extId = bidRequest.params.extId !== undefined ? '&id=' + bidRequest.params.extId : ''; + const adType = matchedBid.adtype !== undefined ? matchedBid.adtype : ''; + const gdprApplies = reqParams.gdpr ? '&gdpr=' + reqParams.gdpr : ''; + const gdprConsent = reqParams.consent ? '&consent=' + reqParams.consent : ''; + const pvId = matchedBid.pvid !== undefined ? '&pvid=' + matchedBid.pvid : ''; + const iabContent = reqParams.iab_content ? '&iab_content=' + reqParams.iab_content : ''; const bidResponse = { requestId: bidRequest.bidId, @@ -152,38 +159,38 @@ export const spec = { referrer: '', ad: ``, meta: { - advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a' - } - } + advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a', + }, + }; if (isVideo(bidRequest, adType)) { - const playersize = getPlayerSize(bidRequest) + const playersize = getPlayerSize(bidRequest); if (playersize) { - bidResponse.width = playersize[0] - bidResponse.height = playersize[1] + bidResponse.width = playersize[0]; + bidResponse.height = playersize[1]; } - bidResponse.mediaType = VIDEO - bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}` + bidResponse.mediaType = VIDEO; + bidResponse.vastUrl = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}${iabContent}`; if (isOutstream(bidRequest)) { const renderer = Renderer.install({ id: bidRequest.bidId, url: OUTSTREAMPLAYER_URL, - loaded: false - }) - renderer.setRender(outstreamRender) - bidResponse.renderer = renderer + loaded: false, + }); + renderer.setRender(outstreamRender); + bidResponse.renderer = renderer; } } if (isNative(bidRequest, adType)) { // there may be publishers still rely on it - const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` - bidResponse.adUrl = url - bidResponse.mediaType = NATIVE - const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2) + const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}`; + bidResponse.adUrl = url; + bidResponse.mediaType = NATIVE; + const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2); const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : { url: '', w: 0, h: 0 }; - const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1) - const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3) + const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1); + const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3); bidResponse.native = { title: nativeTitleAsset ? nativeTitleAsset.title.text : '', body: nativeBodyAsset ? nativeBodyAsset.data.value : '', @@ -197,10 +204,10 @@ export const spec = { }; } - bidResponses.push(bidResponse) + bidResponses.push(bidResponse); } - }) - return bidResponses + }); + return bidResponses; }, /** @@ -212,13 +219,13 @@ export const spec = { * @param {string} uspConsent Is the US Privacy Consent string. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; if (syncOptions.iframeEnabled) { const params = []; params.push(`ts=${timestamp()}`); - params.push(`type=h`) + params.push(`type=h`); if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); } @@ -227,12 +234,12 @@ export const spec = { } syncs.push({ type: 'iframe', - url: `${ENDPOINT}/d/6846326/766/2x2?${params.join('&')}` + url: `${ENDPOINT}/d/6846326/766/2x2?${params.join('&')}`, }); } return syncs; - } + }, }; /** @@ -242,7 +249,7 @@ export const spec = { * @returns {Boolean} */ function isVideo(format, adtype) { - return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' + return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video'; } /** @@ -252,7 +259,7 @@ function isVideo(format, adtype) { * @returns {Boolean} */ function isNative(format, adtype) { - return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native' + return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native'; } /** @@ -261,8 +268,8 @@ function isNative(format, adtype) { * @returns {Boolean} */ function isOutstream(format) { - const context = deepAccess(format, 'mediaTypes.video.context') - return (context === 'outstream') + const context = deepAccess(format, 'mediaTypes.video.context'); + return (context === 'outstream'); } /** @@ -271,30 +278,45 @@ function isOutstream(format) { * @returns {Array} */ function getPlayerSize(format) { - const playerSize = deepAccess(format, 'mediaTypes.video.playerSize') - return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize + const playerSize = deepAccess(format, 'mediaTypes.video.playerSize'); + return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize; } /** - * Expands a 'WxH' string as a 2-element [W, H] array + * Expands a 'WxH' string to a 2-element [W, H] array * @param {String} size * @returns {Array} */ function parseSize(size) { - return size.split(DIMENSION_SIGN).map(Number) + return size.split(DIMENSION_SIGN).map(Number); } /** * Creates a string out of an array of eids with source and uid - * @param {Array} eids + * @param {Array.<{source: String, uids: Array.<{id: String, atype: Number, ext: Object}>}>} eids * @returns {String} */ function createUserIdString(eids) { - const str = [] + const str = []; for (let i = 0; i < eids.length; i++) { - str.push(eids[i].source + ':' + eids[i].uids[0].id) + str.push(eids[i].source + ':' + eids[i].uids[0].id); } - return str.join(',') + return str.join(','); +} + +/** + * Creates a string from an array of eids with ID provider and atype if atype exists + * @param {Array.<{source: String, uids: Array.<{id: String, atype: Number, ext: Object}>}>} eids + * @returns {String} idprovider:atype,idprovider2:atype2,... + */ +function createUserIdAtypesString(eids) { + const str = []; + for (let i = 0; i < eids.length; i++) { + if (eids[i].uids[0].atype) { + str.push(eids[i].source + ':' + eids[i].uids[0].atype); + } + } + return str.join(','); } /** @@ -303,18 +325,18 @@ function createUserIdString(eids) { * @returns {String} */ function createQueryString(obj) { - const str = [] + const str = []; for (const p in obj) { if (obj.hasOwnProperty(p)) { - const val = obj[p] + const val = obj[p]; if (p !== 'schain' && p !== 'iab_content') { - str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)) + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)); } else { - str.push(p + '=' + val) + str.push(p + '=' + val); } } } - return str.join('&') + return str.join('&'); } /** @@ -323,15 +345,15 @@ function createQueryString(obj) { * @returns {String} */ function createTargetingString(obj) { - const str = [] + const str = []; for (const p in obj) { if (obj.hasOwnProperty(p)) { - const key = p - const val = obj[p] - str.push(key + '=' + val) + const key = p; + const val = obj[p]; + str.push(key + '=' + val); } } - return str.join('&') + return str.join('&'); } /** @@ -340,13 +362,13 @@ function createTargetingString(obj) { * @returns {String} */ function createSchainString(schain) { - const ver = schain.ver || '' - const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : '' - const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'] + const ver = schain.ver || ''; + const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : ''; + const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext']; const nodesString = schain.nodes.reduce((acc, node) => { - return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}` - }, '') - return `${ver},${complete}${nodesString}` + return acc += `!${keys.map(key => node[key] ? encodeURIComponentWithBangIncluded(node[key]) : '').join(',')}`; + }, ''); + return `${ver},${complete}${nodesString}`; } /** @@ -358,33 +380,44 @@ function createSchainString(schain) { */ function getContentObject(bid) { if (bid.params.iabContent && isPlainObject(bid.params.iabContent)) { - return bid.params.iabContent + return bid.params.iabContent; } const globalContent = deepAccess(bid, 'ortb2.site') ? deepAccess(bid, 'ortb2.site.content') - : deepAccess(bid, 'ortb2.app.content') + : deepAccess(bid, 'ortb2.app.content'); if (globalContent && isPlainObject(globalContent)) { - return globalContent + return globalContent; } - return undefined + return undefined; } /** - * Creates a string for iab_content object + * Creates a string for iab_content object by + * 1. flatten the iab content object + * 2. encoding the values + * 3. joining array of defined keys ('keyword', 'cat') into one value seperated with '|' + * 4. encoding the whole string * @param {Object} iabContent * @returns {String} */ function createIabContentString(iabContent) { - const arrKeys = ['keywords', 'cat'] - const str = [] - for (const key in iabContent) { - if (iabContent.hasOwnProperty(key)) { - const value = (arrKeys.indexOf(key) !== -1 && Array.isArray(iabContent[key])) - ? iabContent[key].map(node => encodeURIComponent(node)).join('|') : encodeURIComponent(iabContent[key]) - str.push(''.concat(key, ':', value)) + const arrKeys = ['keywords', 'cat']; + const str = []; + const transformObjToParam = (obj = {}, extraKey = '') => { + for (const key in obj) { + if ((arrKeys.indexOf(key) !== -1 && Array.isArray(obj[key]))) { + // Array of defined keyword which have to be joined into one value from "key: [value1, value2, value3]" to "key:value1|value2|value3" + str.push(''.concat(key, ':', obj[key].map(node => encodeURIComponent(node)).join('|'))); + } else if (typeof obj[key] !== 'object') { + str.push(''.concat(extraKey + key, ':', encodeURIComponent(obj[key]))); + } else { + // Object has to be further flattened + transformObjToParam(obj[key], ''.concat(extraKey, key, '.')); + } } - } - return encodeURIComponent(str.join(',')) + return str.join(','); + }; + return encodeURIComponent(transformObjToParam(iabContent)); } /** @@ -393,7 +426,7 @@ function createIabContentString(iabContent) { * @returns {String} */ function encodeURIComponentWithBangIncluded(str) { - return encodeURIComponent(str).replace(/!/g, '%21') + return encodeURIComponent(str).replace(/!/g, '%21'); } /** @@ -402,11 +435,11 @@ function encodeURIComponentWithBangIncluded(str) { */ function outstreamRender(bid) { bid.renderer.push(() => { - window.ma_width = bid.width - window.ma_height = bid.height - window.ma_vastUrl = bid.vastUrl - window.ma_container = bid.adUnitCode - window.document.dispatchEvent(new Event('ma-start-event')) + window.ma_width = bid.width; + window.ma_height = bid.height; + window.ma_vastUrl = bid.vastUrl; + window.ma_container = bid.adUnitCode; + window.document.dispatchEvent(new Event('ma-start-event')); }); } @@ -417,33 +450,33 @@ function outstreamRender(bid) { * @returns {string[]} */ function extractSizes(bid) { - const { mediaTypes } = bid // see https://docs.prebid.org/dev-docs/adunit-reference.html#examples - const sizes = [] + const { mediaTypes } = bid; // see https://docs.prebid.org/dev-docs/adunit-reference.html#examples + const sizes = []; if (isPlainObject(mediaTypes)) { - const { [BANNER]: bannerType } = mediaTypes + const { [BANNER]: bannerType } = mediaTypes; // only applies for multi size Adslots -> BANNER if (bannerType && isArray(bannerType.sizes)) { if (isArray(bannerType.sizes[0])) { // multiple sizes given - sizes.push(bannerType.sizes) + sizes.push(bannerType.sizes); } else { // just one size provided as array -> wrap to uniformly flatten later - sizes.push([bannerType.sizes]) + sizes.push([bannerType.sizes]); } } // The bid top level field `sizes` is deprecated and should not be used anymore. Keeping it for compatibility. } else if (isArray(bid.sizes)) { if (isArray(bid.sizes[0])) { - sizes.push(bid.sizes) + sizes.push(bid.sizes); } else { - sizes.push([bid.sizes]) + sizes.push([bid.sizes]); } } /** @type {Set} */ - const deduplicatedSizeStrings = new Set(sizes.flat().map(([width, height]) => width + DIMENSION_SIGN + height)) + const deduplicatedSizeStrings = new Set(sizes.flat().map(([width, height]) => width + DIMENSION_SIGN + height)); - return Array.from(deduplicatedSizeStrings) + return Array.from(deduplicatedSizeStrings); } /** @@ -452,7 +485,7 @@ function extractSizes(bid) { * * @param {Object} bid * @param {string[]} sizes - * @returns The floor CPM of a matched rule based on the rule selection process (mediaType, size and currency), + * @returns The floor CPM in cents of a matched rule based on the rule selection process (mediaType, size and currency), * using the getFloor() inputs. Multi sizes and unsupported media types will default to '*' */ function getBidFloor(bid, sizes) { @@ -464,12 +497,12 @@ function getBidFloor(bid, sizes) { const floor = bid.getFloor({ currency: CURRENCY_CODE, mediaType: mediaType !== undefined && spec.supportedMediaTypes.includes(mediaType) ? mediaType : '*', - size: sizes.length !== 1 ? '*' : extractSizes(sizes) + size: sizes.length !== 1 ? '*' : sizes[0].split(DIMENSION_SIGN), }); if (floor.currency === CURRENCY_CODE) { - return floor.floor; + return (floor.floor * 100).toFixed(0); } return undefined; } -registerBidder(spec) +registerBidder(spec); diff --git a/modules/yieldliftBidAdapter.js b/modules/yieldliftBidAdapter.js index 160d7e9a009..6e4bce15187 100644 --- a/modules/yieldliftBidAdapter.js +++ b/modules/yieldliftBidAdapter.js @@ -2,9 +2,9 @@ import {deepSetValue, logInfo, deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -const ENDPOINT_URL = 'https://x.yieldlift.com/auction'; +const ENDPOINT_URL = 'https://x.yieldlift.com/pbjs'; -const DEFAULT_BID_TTL = 30; +const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; @@ -98,7 +98,7 @@ export const spec = { width: bid.w, height: bid.h, ad: bid.adm, - ttl: DEFAULT_BID_TTL, + ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, creativeId: bid.crid, netRevenue: DEFAULT_NET_REVENUE, currency: DEFAULT_CURRENCY, diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 892936ceea5..b4b916a1048 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -20,12 +20,15 @@ import {find, includes} from '../src/polyfill.js'; import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'yieldmo'; +const GVLID = 173; const CURRENCY = 'USD'; const TIME_TO_LIVE = 300; const NET_REVENUE = true; -const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; -const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; const PB_COOKIE_ASSIST_SYNC_ENDPOINT = `https://ads.yieldmo.com/pbcas`; +const BANNER_PATH = '/exchange/prebid'; +const VIDEO_PATH = '/exchange/prebidvideo'; +const STAGE_DOMAIN = 'https://ads-stg.yieldmo.com'; +const PROD_DOMAIN = 'https://ads.yieldmo.com'; const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js'; const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable']; @@ -40,7 +43,7 @@ const BANNER_REQUEST_PROPERTIES_TO_REDUCE = ['description', 'title', 'pr', 'page export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], - + gvlid: GVLID, /** * Determines whether or not the given bid request is valid. * @param {object} bid, bid to validate @@ -59,6 +62,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { + const stage = isStage(bidderRequest); + const bannerUrl = getAdserverUrl(BANNER_PATH, stage); + const videoUrl = getAdserverUrl(VIDEO_PATH, stage); const bannerBidRequests = bidRequests.filter(request => hasBannerMediaType(request)); const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request)); let serverRequests = []; @@ -122,8 +128,8 @@ export const spec = { serverRequest.eids = JSON.stringify(eids); }; // check if url exceeded max length - const url = `${BANNER_SERVER_ENDPOINT}?${parseQueryStringParameters(serverRequest)}`; - let extraCharacters = url.length - MAX_BANNER_REQUEST_URL_LENGTH; + const fullUrl = `${bannerUrl}?${parseQueryStringParameters(serverRequest)}`; + let extraCharacters = fullUrl.length - MAX_BANNER_REQUEST_URL_LENGTH; if (extraCharacters > 0) { for (let i = 0; i < BANNER_REQUEST_PROPERTIES_TO_REDUCE.length; i++) { extraCharacters = shortcutProperty(extraCharacters, serverRequest, BANNER_REQUEST_PROPERTIES_TO_REDUCE[i]); @@ -136,7 +142,7 @@ export const spec = { serverRequests.push({ method: 'GET', - url: BANNER_SERVER_ENDPOINT, + url: bannerUrl, data: serverRequest }); } @@ -148,7 +154,7 @@ export const spec = { }; serverRequests.push({ method: 'POST', - url: VIDEO_SERVER_ENDPOINT, + url: videoUrl, data: serverRequest }); } @@ -249,7 +255,6 @@ function addPlacement(request) { if (transactionId) { placementInfo.tid = transactionId; } - if (request.auctionId) { placementInfo.auctionId = request.auctionId; } @@ -377,10 +382,11 @@ function openRtbRequest(bidRequests, bidderRequest) { const schain = bidRequests[0].schain; let openRtbRequest = { id: bidRequests[0].bidderRequestId, + tmax: bidderRequest.timeout || 400, at: 1, imp: bidRequests.map(bidRequest => openRtbImpression(bidRequest)), site: openRtbSite(bidRequests[0], bidderRequest), - device: openRtbDevice(bidRequests[0]), + device: deepAccess(bidderRequest, 'ortb2.device'), badv: bidRequests[0].params.badv || [], bcat: deepAccess(bidderRequest, 'bcat') || bidRequests[0].params.bcat || [], ext: { @@ -502,17 +508,6 @@ function openRtbSite(bidRequest, bidderRequest) { return result; } -/** - * @return Object OpenRTB's 'device' object - */ -function openRtbDevice(bidRequest) { - const deviceObj = { - ua: navigator.userAgent, - language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), - }; - return deviceObj; -} - /** * Updates openRtbRequest with GDPR info from bidderRequest, if present. * @param {Object} openRtbRequest OpenRTB's request to update. @@ -676,3 +671,12 @@ function canAccessTopWindow() { return false; } } + +function isStage(bidderRequest) { + return !!bidderRequest.refererInfo?.referer?.includes('pb_force_a'); +} + +function getAdserverUrl(path, stage) { + const domain = stage ? STAGE_DOMAIN : PROD_DOMAIN; + return `${domain}${path}`; +} diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index c07911c9e1f..d408a0595f9 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -4,6 +4,17 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory').BidderSpec} BidderSpec + * @typedef {import('../src/adapters/bidderFactory').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory').UserSync} UserSync + * @typedef {import('../src/auction').BidderRequest} BidderRequest + */ + const BIDDER_CODE = 'yieldone'; const ENDPOINT_URL = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; @@ -13,13 +24,25 @@ const VIEWABLE_PERCENTAGE_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstvi const DEFAULT_VIDEO_SIZE = {w: 640, h: 360}; +/** @type BidderSpec */ export const spec = { code: BIDDER_CODE, aliases: ['y1'], supportedMediaTypes: [BANNER, VIDEO], + /** + * Determines whether or not the given bid request is valid. + * @param {BidRequest} bid The bid params to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { return !!(bid.params.placementId); }, + /** + * Make a server request from the list of BidRequests. + * @param {Bid[]} validBidRequests - An array of bids. + * @param {BidderRequest} bidderRequest - bidder request object. + * @returns {ServerRequest|ServerRequest[]} ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { const params = bidRequest.params; @@ -74,10 +97,13 @@ export const spec = { } // DACID - const dacId = deepAccess(bidRequest, 'userId.dacId.id'); - if (isStr(dacId) && !isEmpty(dacId)) { - payload.dac_id = dacId; - payload.fuuid = dacId; + const fuuid = deepAccess(bidRequest, 'userId.dacId.fuuid'); + const dacid = deepAccess(bidRequest, 'userId.dacId.id'); + if (isStr(fuuid) && !isEmpty(fuuid)) { + payload.fuuid = fuuid; + } + if (isStr(dacid) && !isEmpty(dacid)) { + payload.dac_id = dacid; } // ID5 @@ -93,6 +119,12 @@ export const spec = { }; }); }, + /** + * Unpack the response from the server into a list of bids. + * @param {ServerResponse} serverResponse - A successful response from the server. + * @param {BidRequest} bidRequests + * @returns {Bid[]} - An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { const bidResponses = []; const response = serverResponse.body; @@ -185,6 +217,11 @@ export const spec = { } return bidResponses; }, + /** + * Register the user sync pixels which should be dropped after the auction. + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @returns {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: function(syncOptions) { if (syncOptions.iframeEnabled) { return [{ @@ -199,7 +236,7 @@ export const spec = { * NOTE: server side does not yet support multiple formats. * @param {Object} bidRequest - * @param {boolean} [enabledOldFormat = true] - default: `true`. - * @return {string|null} - `"banner"` or `"video"` or `null`. + * @returns {string|null} - `"banner"` or `"video"` or `null`. */ function getMediaType(bidRequest, enabledOldFormat = true) { let hasBannerType = Boolean(deepAccess(bidRequest, 'mediaTypes.banner')); @@ -236,7 +273,7 @@ function getMediaType(bidRequest, enabledOldFormat = true) { * @param {Object} bidRequest.banner - * @param {Array} bidRequest.banner.sizes - * @param {boolean} [enabledOldFormat = true] - default: `true`. - * @return {string} - strings like `"300x250"` or `"300x250,728x90"`. + * @returns {string} - strings like `"300x250"` or `"300x250,728x90"`. */ function getBannerSizes(bidRequest, enabledOldFormat = true) { let sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); @@ -251,10 +288,10 @@ function getBannerSizes(bidRequest, enabledOldFormat = true) { /** * @param {Object} bidRequest - * @param {boolean} [enabledOldFormat = true] - default: `true`. - * @param {boolean} [enabledFlux = true] - default: `true`. - * @return {{w: number, h: number}} - + * @param {boolean} [enabled1x1 = true] - default: `true`. + * @returns {{w: number, h: number}} - */ -function getVideoSize(bidRequest, enabledOldFormat = true, enabledFlux = true) { +function getVideoSize(bidRequest, enabledOldFormat = true, enabled1x1 = true) { /** * @param {Array | Array>} sizes - * @return {{w: number, h: number} | null} - @@ -284,10 +321,10 @@ function getVideoSize(bidRequest, enabledOldFormat = true, enabledFlux = true) { playerSize = playerSize || _getPlayerSize(bidRequest.sizes); } - if (enabledFlux) { - // NOTE: `video.playerSize` in Flux is always [1,1]. + if (enabled1x1) { + // NOTE: `video.playerSize` in 1x1 is always [1,1]. if (playerSize && (playerSize.w === 1 && playerSize.h === 1)) { - // NOTE: `params.playerSize` is a specific object to support `FLUX`. + // NOTE: `params.playerSize` is a specific object to support `1x1`. playerSize = _getPlayerSize(deepAccess(bidRequest, 'params.playerSize')); } } @@ -295,6 +332,11 @@ function getVideoSize(bidRequest, enabledOldFormat = true, enabledFlux = true) { return playerSize || DEFAULT_VIDEO_SIZE; } +/** + * Create render for outstream video. + * @param {Object} serverResponse.body - + * @returns {Renderer} - Prebid Renderer object + */ function newRenderer(response) { const renderer = Renderer.install({ id: response.uid, @@ -311,12 +353,21 @@ function newRenderer(response) { return renderer; } +/** + * Handles an outstream response after the library is loaded + * @param {Object} bid + */ function outstreamRender(bid) { bid.renderer.push(() => { window.DACIVTPREBID.renderPrebid(bid); }); } +/** + * Create render for cmer outstream video. + * @param {Object} serverResponse.body - + * @returns {Renderer} - Prebid Renderer object + */ function newCmerRenderer(response) { const renderer = Renderer.install({ id: response.uid, @@ -333,6 +384,10 @@ function newCmerRenderer(response) { return renderer; } +/** + * Handles an outstream response after the library is loaded + * @param {Object} bid + */ function cmerRender(bid) { bid.renderer.push(() => { window.CMERYONEPREBID.renderPrebid(bid); diff --git a/modules/yieldoneBidAdapter.md b/modules/yieldoneBidAdapter.md index 1414d4e464f..d1306a08202 100644 --- a/modules/yieldoneBidAdapter.md +++ b/modules/yieldoneBidAdapter.md @@ -13,8 +13,7 @@ Connect to YIELDONE for bids. THE YIELDONE adapter requires setup and approval from the YIELDONE team. Please reach out to your account team or y1s@platform-one.co.jp for more information. -Note: THE YIELDONE adapter do not support "multi-format" scenario... if both -banner and video are specified as mediatypes, YIELDONE will treat it as a video unit. +YIELDONE adapter supports Banner, Video and Multi-Format(video, banner) currently. # Test Parameters ```javascript @@ -24,13 +23,16 @@ var adUnits = [ code: 'banner-div', mediaTypes: { banner: { - sizes: [[300, 250], [336, 280]] + sizes: [ + [300, 250], + [336, 280] + ] } }, bids: [{ bidder: 'yieldone', params: { - placementId: '36891' + placementId: '36891' // required } }] }, @@ -44,11 +46,109 @@ var adUnits = [ }, }, bids: [{ - bidder: 'yieldone', - params: { - placementId: '41993' - } - }] + bidder: "yieldone", + params: { + placementId: "36892", // required + playerParams: { // optional + wrapperWidth: "320px", // optional + wrapperHeight: "180px" // optional + }, + } + }] + }, + // Video adUnit(mediaTypes.video.playerSize: [1,1]) + { + code: 'video-1x1-div', + mediaTypes: { + video: { + playerSize: [[1, 1]], + context: 'outstream' + }, + }, + bids: [{ + bidder: 'yieldone', + params: { + placementId: "36892", // required + playerSize: [640, 360], // required + playerParams: { // optional + wrapperWidth: "320px", // optional + wrapperHeight: "180px" // optional + }, + } + }] + }, + // Multi-Format adUnit + { + code: "multi-format-div", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [1, 1] + ] + }, + video: { + playerSize: [640, 360], + context: "outstream" + } + }, + bids: [{ + // * "video" bid object should be placed before "banner" bid object. + // This bid will request a "video" media type ad. + bidder: "yieldone", + params: { + placementId: "36892", // required + playerParams: { // required + wrapperWidth: "320px", // optional + wrapperHeight: "180px" // optional + }, + } + }, + { + // This bid will request a "banner" media type ad. + bidder: "yieldone", + params: { + placementId: "36891" // required + } + } + ] + }, + // Multi-Format adUnit(mediaTypes.video.playerSize: [1,1]) + { + code: "multi-format-1x1-div", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [1, 1] + ] + }, + video: { + playerSize: [1, 1], + context: "outstream" + } + }, + bids: [{ + // * "video" bid object should be placed before "banner" bid object. + // This bid will request a "video" media type ad. + bidder: "yieldone", + params: { + placementId: "36892", // required + playerSize: [640, 360], // required + playerParams: { // required + wrapperWidth: "320px", // optional + wrapperHeight: "180px" // optional + }, + } + }, + { + // This bid will request a "banner" media type ad. + bidder: "yieldone", + params: { + placementId: "36891" // required + } + } + ] } ``` diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 4689683fbc7..74ddfc0bd06 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -90,6 +90,21 @@ export const spec = { if (!impData.banner && !impData.video) { impData.banner = buildBanner(request); } + + if (typeof request.getFloor === 'function') { + const floorInfo = request.getFloor({ + currency: 'USD', + mediaType: impData.video ? 'video' : 'banner', + size: [ impData.video ? impData.video.w : impData.banner.w, impData.video ? impData.video.h : impData.banner.h ] + }); + if (floorInfo && floorInfo.floor) { + impData.bidfloor = floorInfo.floor; + } + } + if (!impData.bidfloor && params.bidfloor) { + impData.bidfloor = params.bidfloor; + } + return impData; }); @@ -129,6 +144,10 @@ export const spec = { deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + if (bidderRequest?.timeout) { + payload.tmax = bidderRequest.timeout; + } + provideEids(validBidRequests[0], payload); const url = params.shortname ? ENDPOINT_URL.concat('?shortname=', params.shortname) : ENDPOINT_URL; return { diff --git a/modules/zeusPrimeRtdProvider.js b/modules/zeusPrimeRtdProvider.js new file mode 100644 index 00000000000..2a455a14f7b --- /dev/null +++ b/modules/zeusPrimeRtdProvider.js @@ -0,0 +1,37 @@ +import { logWarn } from '../src/utils.js' +import { submodule } from '../src/hook.js' + +function initModule() { + logWarn('Zeus Prime has been deprecated. This module will be removed in Prebid 8.') +} + +/** + * @preserve + * @type {RtdSubmodule} + */ +const zeusPrimeSubmodule = { + /** + * @preserve + * The name of the plugin. + * @type {string} + */ + name: 'zeusPrime', + + /** + * @preserve + * ZeusPrime use + */ + init: initModule, +} + +/** + * @preserve + * Register the Sub Module. + */ +function registerSubModule() { + submodule('realTimeData', zeusPrimeSubmodule) +} + +registerSubModule() + +export { zeusPrimeSubmodule } diff --git a/modules/zeusPrimeRtdProvider.md b/modules/zeusPrimeRtdProvider.md new file mode 100644 index 00000000000..40b44e76f1c --- /dev/null +++ b/modules/zeusPrimeRtdProvider.md @@ -0,0 +1,63 @@ +# Overview + +# NOTE: ZEUS PRIME HAS BEEN DEPRECATED! +# THIS MODULE WILL BE REMOVED IN PREBID 8. + +Module Name: Zeus Prime RTD Provider +Module Type: Rtd Provider +Maintainer: support@zeustechnology.com + +## Description + +The Zeus Prime RTD Provider provides integration of Zeus Prime onto sites with Prebid. This module will request information from Zeus Prime servers to add the page level targeting required for Prime into the customer's ad setup. + +Zeus Prime runs as soon as the code is initialized, so it can retrieve the information required from the Zeus Prime server to create the targeting key-values. Zeus Prime will provide two page level key-values: `zeus_` and `zeus_insights`. Zeus Prime provides contextual information about a pages content, and does not provide user information that could present privacy implications. + +For more information and help with setting up Zeus Prime, see the [onboarding documentation site](https://onboarding.zeustechnology.com). + +## Usage + +To use Zeus Prime, add `zeusPrimeRtdProvider` into your Prebid build: + +``` +gulp build --modules=rtdModule,zeusPrimeRtdProvider +``` + +> Note that the global RTD module, `rtdModule`, is required for the Zeus Prime RTD module. + +Once the code is included, configure Zeus Prime in your Prebid configuration: + +```javascript +pbjs.setConfig({ + ..., + realTimeData: { + dataProviders: [{ + name: 'zeusPrime', + waitForIt: false, + params: { + gamId: '' + } + }] + }, + ... +}) +``` + +## Parameters + +The parameters below describe the configuration object used to configure Zeus Prime. + +| Name | Type | Description | Default | +|------------------------|----------|-----------------------------------------------------------------------------------------------|---------| +| name | String | This will always be `zeusPrime` | - | +| waitForIt | Boolean | Should be false since Zeus Prime runs on initial load and not during the bidding cycle. | `false` | +| params | Object | | - | +| params.gamId | String | The gamId or Google Ad Manager Network Code to access your Google Ad Manager instance. | - | + +### `gamId` Parameter + +Zeus Prime requires the gamId parameter, or the Google Ad Manager Network Code, to reference your account. See the [Google documentation](https://support.google.com/admanager/answer/7674889?hl=en) to find out where you can retrieve the Network Code. + +## Troubleshooting + +For troubleshooting steps and guides to assist with verifying your Zeus Prime installation, see our [installation documentation](https://onboarding.zeustechnology.com/docs/installation). \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2b5660d6f98..a2f317e9c2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1273 +1,931 @@ { "name": "prebid.js", - "version": "7.19.0", - "lockfileVersion": 2, + "version": "7.37.0", + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "prebid.js", - "version": "7.19.0", - "license": "Apache-2.0", - "dependencies": { - "@babel/core": "^7.16.7", - "@babel/plugin-transform-runtime": "^7.18.9", - "@babel/preset-env": "^7.16.8", - "@babel/runtime": "^7.18.9", - "core-js": "^3.13.0", - "core-js-pure": "^3.13.0", - "criteo-direct-rsa-validate": "^1.1.0", - "crypto-js": "^3.3.0", - "dlv": "1.1.3", - "dset": "3.1.2", - "express": "^4.15.4", - "fun-hooks": "^0.9.9", - "just-clone": "^1.0.2", - "live-connect-js": "2.4.0" - }, - "devDependencies": { - "@babel/eslint-parser": "^7.16.5", - "@wdio/browserstack-service": "^7.16.0", - "@wdio/cli": "^7.5.2", - "@wdio/concise-reporter": "^7.5.2", - "@wdio/local-runner": "^7.5.2", - "@wdio/mocha-framework": "^7.5.2", - "@wdio/spec-reporter": "^7.19.0", - "@wdio/sync": "^7.5.2", - "ajv": "6.12.3", - "assert": "^2.0.0", - "babel-loader": "^8.0.5", - "babel-plugin-istanbul": "^6.1.1", - "babel-register": "^6.26.0", - "body-parser": "^1.19.0", - "chai": "^4.2.0", - "coveralls": "^3.1.0", - "deep-equal": "^2.0.3", - "documentation": "^14.0.0", - "es5-shim": "^4.5.14", - "eslint": "^7.27.0", - "eslint-config-standard": "^10.2.1", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prebid": "file:./plugins/eslint", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-standard": "^3.0.1", - "execa": "^1.0.0", - "faker": "^5.5.3", - "fs.extra": "^1.3.2", - "gulp": "^4.0.0", - "gulp-clean": "^0.3.2", - "gulp-concat": "^2.6.0", - "gulp-connect": "^5.7.0", - "gulp-eslint": "^6.0.0", - "gulp-if": "^3.0.0", - "gulp-js-escape": "^1.0.1", - "gulp-replace": "^1.0.0", - "gulp-shell": "^0.8.0", - "gulp-sourcemaps": "^3.0.0", - "gulp-terser": "^2.0.1", - "gulp-util": "^3.0.0", - "is-docker": "^2.2.1", - "istanbul": "^0.4.5", - "karma": "^6.3.2", - "karma-babel-preprocessor": "^8.0.1", - "karma-browserstack-launcher": "1.4.0", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^3.1.0", - "karma-coverage": "^2.0.1", - "karma-coverage-istanbul-reporter": "^3.0.3", - "karma-es5-shim": "^0.0.4", - "karma-firefox-launcher": "^2.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-opera-launcher": "^1.0.0", - "karma-safari-launcher": "^1.0.0", - "karma-script-launcher": "^1.0.0", - "karma-sinon": "^1.0.5", - "karma-sourcemap-loader": "^0.3.7", - "karma-spec-reporter": "^0.0.32", - "karma-webpack": "^5.0.0", - "lodash": "^4.17.21", - "mocha": "^5.0.0", - "morgan": "^1.10.0", - "opn": "^5.4.0", - "resolve-from": "^5.0.0", - "sinon": "^4.1.3", - "through2": "^4.0.2", - "url": "^0.11.0", - "url-parse": "^1.0.5", - "webdriverio": "^7.6.1", - "webpack": "^5.70.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-manifest-plugin": "^5.0.0", - "webpack-stream": "^7.0.0", - "yargs": "^1.3.1" - }, - "engines": { - "node": ">=8.9.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.0" - }, - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "node_modules/@babel/code-frame": { + "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dependencies": { + "requires": { "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz", - "integrity": "sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==", - "engines": { - "node": ">=6.9.0" - } + "@babel/compat-data": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", + "integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==" }, - "node_modules/@babel/core": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", - "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", - "dependencies": { + "@babel/core": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", + "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", + "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.0", + "@babel/generator": "^7.19.6", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helpers": "^7.19.4", + "@babel/parser": "^7.19.6", "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0", + "@babel/traverse": "^7.19.6", + "@babel/types": "^7.19.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.1", "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/eslint-parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz", - "integrity": "sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA==", + "@babel/eslint-parser": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", + "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", "dev": true, - "dependencies": { - "eslint-scope": "^5.1.1", + "requires": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", "semver": "^6.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" } }, - "node_modules/@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "dependencies": { - "@babel/types": "^7.19.0", + "@babel/generator": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.1.tgz", + "integrity": "sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg==", + "requires": { + "@babel/types": "^7.20.0", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "requires": { + "@babel/types": "^7.18.6" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", - "integrity": "sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==", - "dependencies": { - "@babel/compat-data": "^7.19.0", + "@babel/helper-compilation-targets": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", + "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "requires": { + "@babel/compat-data": "^7.20.0", "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", + "browserslist": "^4.21.3", "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", - "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-create-class-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", + "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6" } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", - "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^5.0.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-create-regexp-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2", "semver": "^6.1.2" } }, - "node_modules/@babel/helper-environment-visitor": { + "@babel/helper-environment-visitor": { "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "engines": { - "node": ">=6.9.0" - } + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "requires": { + "@babel/types": "^7.18.6" } }, - "node_modules/@babel/helper-function-name": { + "@babel/helper-function-name": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "dependencies": { + "requires": { "@babel/template": "^7.18.10", "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { + "@babel/helper-hoist-variables": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dependencies": { + "requires": { "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "requires": { + "@babel/types": "^7.18.9" } }, - "node_modules/@babel/helper-module-imports": { + "@babel/helper-module-imports": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dependencies": { + "requires": { "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "dependencies": { + "@babel/helper-module-transforms": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz", + "integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==", + "requires": { "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-simple-access": "^7.19.4", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" + "@babel/traverse": "^7.19.6", + "@babel/types": "^7.19.4" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "requires": { + "@babel/types": "^7.18.6" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", - "engines": { - "node": ">=6.9.0" - } + "@babel/helper-plugin-utils": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", + "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==" }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-simple-access": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz", + "integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==", + "requires": { + "@babel/types": "^7.19.4" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "requires": { + "@babel/types": "^7.20.0" } }, - "node_modules/@babel/helper-split-export-declaration": { + "@babel/helper-split-export-declaration": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dependencies": { + "requires": { "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "engines": { - "node": ">=6.9.0" - } + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "engines": { - "node": ">=6.9.0" - } + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, - "node_modules/@babel/helper-validator-option": { + "@babel/helper-validator-option": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "dependencies": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" - } + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" }, - "node_modules/@babel/helpers": { + "@babel/helper-wrap-function": { "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", - "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", - "dependencies": { + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "requires": { + "@babel/helper-function-name": "^7.19.0", "@babel/template": "^7.18.10", "@babel/traverse": "^7.19.0", "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { + "@babel/helpers": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", + "integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", + "requires": { + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.0" + } + }, + "@babel/highlight": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dependencies": { + "requires": { "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } + "@babel/parser": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.1.tgz", + "integrity": "sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw==" }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", - "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", - "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", - "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.17.6", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", - "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", - "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", - "dependencies": { - "@babel/compat-data": "^7.17.0", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz", + "integrity": "sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q==", + "requires": { + "@babel/compat-data": "^7.19.4", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.18.8" } }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", - "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=4" + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-syntax-async-generators": { + "@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { + "@babel/plugin-syntax-class-properties": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.12.13" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { + "@babel/plugin-syntax-class-static-block": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { + "@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { + "@babel/plugin-syntax-export-namespace-from": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.3" } }, - "node_modules/@babel/plugin-syntax-json-strings": { + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { + "@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.10.4" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { + "@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { + "@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { + "@babel/plugin-syntax-private-property-in-object": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { + "@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-block-scoping": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.0.tgz", + "integrity": "sha512-sXOohbpHZSk7GjxK9b3dKB7CfqUD5DwOH+DggKzOQ7TXYP+RCSbRykfjQmn/zq+rBjycVRtLf9pYhAaEJA786w==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/plugin-transform-classes": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", + "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.19.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-replace-supers": "^7.18.9", + "@babel/helper-split-export-declaration": "^7.18.6", "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-destructuring": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.0.tgz", + "integrity": "sha512-1dIhvZfkDVx/zn2S1aFwlruspTt4189j7fEkH0Y0VyuDM6bQt7bD6kLcz3l4IlLG+e5OReaBz9ROAbttRtUHqA==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", - "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", - "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", - "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", - "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", - "dependencies": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", - "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", - "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-parameters": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.1.tgz", + "integrity": "sha512-nDvKLrAvl+kf6BOy1UJ3MGwzzfTMgppxwiD2Jb4LO3xjYyZq30oQzDNJbCQpMdG9+j2IXHoiMrw5Cm/L6ZoxXQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", - "dependencies": { - "regenerator-transform": "^0.14.2" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", - "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz", - "integrity": "sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg==", - "dependencies": { + "@babel/plugin-transform-runtime": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz", + "integrity": "sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==", + "requires": { "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "babel-plugin-polyfill-corejs2": "^0.3.1", - "babel-plugin-polyfill-corejs3": "^0.5.2", - "babel-plugin-polyfill-regenerator": "^0.3.1", + "@babel/helper-plugin-utils": "^7.19.0", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "requires": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" } }, - "node_modules/@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dependencies": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.11", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/preset-env": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.4.tgz", + "integrity": "sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg==", + "requires": { + "@babel/compat-data": "^7.19.4", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.19.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.19.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.18.6", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", @@ -1277,55 +935,52 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.8", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.19.4", + "@babel/plugin-transform-classes": "^7.19.0", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.19.4", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.18.6", + "@babel/plugin-transform-modules-commonjs": "^7.18.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.0", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.18.8", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", + "@babel/types": "^7.19.4", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/preset-modules": { + "@babel/preset-modules": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dependencies": { + "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", "@babel/plugin-transform-dotall-regex": "^7.4.4", @@ -1333,83 +988,73 @@ "esutils": "^2.0.2" } }, - "node_modules/@babel/runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", - "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" + "@babel/runtime": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", + "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", + "requires": { + "regenerator-runtime": "^0.13.10" } }, - "node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "@babel/runtime-corejs3": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.1.tgz", + "integrity": "sha512-CGulbEDcg/ND1Im7fUNRZdGXmX2MTWVVZacQi/6DiKE5HNwZ3aVTm5PV4lO8HHz0B2h8WQyvKKjbX5XgTtydsg==", + "dev": true, + "requires": { + "core-js-pure": "^3.25.1", + "regenerator-runtime": "^0.13.10" + } }, - "node_modules/@babel/template": { + "@babel/template": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dependencies": { + "requires": { "@babel/code-frame": "^7.18.6", "@babel/parser": "^7.18.10", "@babel/types": "^7.18.10" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/traverse": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz", - "integrity": "sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==", - "dependencies": { + "@babel/traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", + "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", + "requires": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", + "@babel/generator": "^7.20.1", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.0", - "@babel/types": "^7.19.0", + "@babel/parser": "^7.20.1", + "@babel/types": "^7.20.0", "debug": "^4.1.0", "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/types": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.0.tgz", + "integrity": "sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg==", + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" } }, - "node_modules/@colors/colors": { + "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } + "dev": true }, - "node_modules/@eslint/eslintrc": { + "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", "dev": true, - "dependencies": { + "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", @@ -1420,21241 +1065,844 @@ "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", - "dev": true, "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, - "node_modules/@gulp-sourcemaps/identity-map": { + "@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", "dev": true, - "dependencies": { + "requires": { "acorn": "^6.4.1", "normalize-path": "^3.0.0", "postcss": "^7.0.16", "source-map": "^0.6.0", "through2": "^3.0.1" }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@gulp-sourcemaps/identity-map/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true + }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, - "node_modules/@gulp-sourcemaps/map-sources": { + "@gulp-sourcemaps/map-sources": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", "dev": true, - "dependencies": { + "requires": { "normalize-path": "^2.0.1", "through2": "^2.0.3" }, - "engines": { - "node": ">= 0.10" + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, - "node_modules/@gulp-sourcemaps/map-sources/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "@hapi/boom": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz", + "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==", "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" + "requires": { + "@hapi/hoek": "9.x.x" } }, - "node_modules/@gulp-sourcemaps/map-sources/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "@hapi/cryptiles": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.1.0.tgz", + "integrity": "sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA==", "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "requires": { + "@hapi/boom": "9.x.x" } }, - "node_modules/@humanwhocodes/config-array": { + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", "dev": true, - "dependencies": { + "requires": { "@humanwhocodes/object-schema": "^1.2.0", "debug": "^4.1.1", "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/object-schema": { + "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@istanbuljs/load-nyc-config": { + "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "dependencies": { + "requires": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" } }, - "node_modules/@istanbuljs/schema": { + "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } + "dev": true }, - "node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", "dev": true, - "dependencies": { + "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", + "@types/yargs": "^15.0.0", "chalk": "^4.0.0" }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", - "engines": { - "node": ">=6.0.0" - } + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, - "node_modules/@jridgewell/set-array": { + "@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "engines": { - "node": ">=6.0.0" - } + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, - "node_modules/@jridgewell/source-map": { + "@jridgewell/source-map": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", "dev": true, - "dependencies": { + "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "requires": { + "eslint-scope": "5.1.1" } }, - "node_modules/@polka/url": { + "@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, - "node_modules/@sindresorhus/is": { + "@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" - } + "dev": true }, - "node_modules/@sinonjs/commons": { + "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "dev": true, - "dependencies": { + "requires": { "type-detect": "4.0.8" } }, - "node_modules/@sinonjs/formatio": { + "@sinonjs/formatio": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", "dev": true, - "dependencies": { + "requires": { "samsam": "1.3.0" } }, - "node_modules/@sinonjs/samsam": { + "@sinonjs/samsam": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", "dev": true, - "dependencies": { + "requires": { "@sinonjs/commons": "^1.3.0", "array-from": "^2.1.1", "lodash": "^4.17.15" } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, - "node_modules/@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true }, - "node_modules/@szmarczak/http-timer": { + "@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", "dev": true, - "dependencies": { + "requires": { "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" } }, - "node_modules/@types/aria-query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.0.tgz", - "integrity": "sha512-P+dkdFu0n08PDIvw+9nT9ByQnd+Udc8DaWPb9HKfaPwCvWvQpC5XaMRx2xLWECm9x1VKNps6vEAlirjA6+uNrQ==", + "@types/aria-query": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", "dev": true }, - "node_modules/@types/cacheable-request": { + "@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", "dev": true, - "dependencies": { + "requires": { "@types/http-cache-semantics": "*", "@types/keyv": "*", "@types/node": "*", "@types/responselike": "*" } }, - "node_modules/@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "node_modules/@types/cookie": { + "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", "dev": true }, - "node_modules/@types/cors": { + "@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", "dev": true }, - "node_modules/@types/debug": { + "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", "dev": true, - "dependencies": { + "requires": { "@types/ms": "*" } }, - "node_modules/@types/diff": { + "@types/diff": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.2.tgz", "integrity": "sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==", "dev": true }, - "node_modules/@types/easy-table": { + "@types/easy-table": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/easy-table/-/easy-table-0.0.33.tgz", "integrity": "sha512-/vvqcJPmZUfQwCgemL0/34G7bIQnCuvgls379ygRlcC1FqNqk3n+VZ15dAO51yl6JNDoWd8vsk+kT8zfZ1VZSw==", "dev": true }, - "node_modules/@types/ejs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.0.tgz", - "integrity": "sha512-DCg+Ka+uDQ31lJ/UtEXVlaeV3d6t81gifaVWKJy4MYVVgvJttyX/viREy+If7fz+tK/gVxTGMtyrFPnm4gjrVA==", + "@types/ejs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.1.tgz", + "integrity": "sha512-RQul5wEfY7BjWm0sYY86cmUN/pcXWGyVxWX93DFFJvcrxax5zKlieLwA3T77xJGwNcZW0YW6CYG70p1m8xPFmA==", "dev": true }, - "node_modules/@types/eslint": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", - "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", + "@types/eslint": { + "version": "8.4.9", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", + "integrity": "sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ==", "dev": true, - "dependencies": { + "requires": { "@types/estree": "*", "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, - "dependencies": { + "requires": { "@types/eslint": "*", "@types/estree": "*" } }, - "node_modules/@types/estree": { + "@types/estree": { "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, - "node_modules/@types/expect": { + "@types/expect": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", "dev": true }, - "node_modules/@types/extend": { + "@types/extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.1.tgz", "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", "dev": true }, - "node_modules/@types/fibers": { + "@types/fibers": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.1.tgz", "integrity": "sha512-yHoUi46uika0snoTpNcVqUSvgbRndaIps4TUCotrXjtc0DHDoPQckmyXEZ2bX3e4mpJmyEW3hRhCwQa/ISCPaA==", "dev": true }, - "node_modules/@types/fs-extra": { + "@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "dev": true, - "dependencies": { + "requires": { "@types/node": "*" } }, - "node_modules/@types/github-slugger": { + "@types/github-slugger": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==", "dev": true }, - "node_modules/@types/hast": { + "@types/hast": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", "dev": true, - "dependencies": { + "requires": { "@types/unist": "*" } }, - "node_modules/@types/http-cache-semantics": { + "@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", "dev": true }, - "node_modules/@types/inquirer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.0.tgz", - "integrity": "sha512-BNoMetRf3gmkpAlV5we+kxyZTle7YibdOntIZbU5pyIfMdcwy784KfeZDAcuyMznkh5OLa17RVXZOGA5LTlkgQ==", + "@types/inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==", "dev": true, - "dependencies": { + "requires": { "@types/through": "*", - "rxjs": "^7.2.0" + "rxjs": "^6.4.0" } }, - "node_modules/@types/istanbul-lib-coverage": { + "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true }, - "node_modules/@types/istanbul-lib-report": { + "@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, - "dependencies": { + "requires": { "@types/istanbul-lib-coverage": "*" } }, - "node_modules/@types/istanbul-reports": { + "@types/istanbul-reports": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, - "dependencies": { + "requires": { "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, - "node_modules/@types/json5": { + "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, - "node_modules/@types/keyv": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", - "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", + "@types/keyv": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-4.2.0.tgz", + "integrity": "sha512-xoBtGl5R9jeKUhc8ZqeYaRDx04qqJ10yhhXYGmJ4Jr8qKpvMsDQQrNUvF/wUJ4klOtmJeJM+p2Xo3zp9uaC3tw==", "dev": true, - "dependencies": { - "@types/node": "*" + "requires": { + "keyv": "*" } }, - "node_modules/@types/lodash": { - "version": "4.14.179", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz", - "integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==", + "@types/lodash": { + "version": "4.14.187", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.187.tgz", + "integrity": "sha512-MrO/xLXCaUgZy3y96C/iOsaIqZSeupyTImKClHunL5GrmaiII2VwvWmLBu2hwa0Kp0sV19CsyjtrTc/Fx8rg/A==", "dev": true }, - "node_modules/@types/lodash.flattendeep": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.6.tgz", - "integrity": "sha512-uLm2MaRVlqJSGsMK0RZpP5T3KqReq+9WbYDHCUhBhp98v56hMG/Yht52bsoTSui9xz2mUvQ9NfG3LrNGDL92Ng==", + "@types/lodash.flattendeep": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.7.tgz", + "integrity": "sha512-1h6GW/AeZw/Wej6uxrqgmdTDZX1yFS39lRsXYkg+3kWvOWWrlGCI6H7lXxlUHOzxDT4QeYGmgPpQ3BX9XevzOg==", "dev": true, - "dependencies": { + "requires": { "@types/lodash": "*" } }, - "node_modules/@types/lodash.pickby": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.6.tgz", - "integrity": "sha512-NFa13XxlMd9eFi0UFZFWIztpMpXhozbijrx3Yb1viYZphT7jyopIFVoIRF4eYMjruWNEG1rnyrRmg/8ej9T8Iw==", + "@types/lodash.pickby": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.7.tgz", + "integrity": "sha512-4ebXRusuLflfscbD0PUX4eVknDHD9Yf+uMtBIvA/hrnTqeAzbuHuDjvnYriLjUrI9YrhCPVKUf4wkRSXJQ6gig==", "dev": true, - "dependencies": { + "requires": { "@types/lodash": "*" } }, - "node_modules/@types/lodash.union": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.6.tgz", - "integrity": "sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw==", + "@types/lodash.union": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.7.tgz", + "integrity": "sha512-6HXM6tsnHJzKgJE0gA/LhTGf/7AbjUk759WZ1MziVm+OBNAATHhdgj+a3KVE8g76GCLAnN4ZEQQG1EGgtBIABA==", "dev": true, - "dependencies": { + "requires": { "@types/lodash": "*" } }, - "node_modules/@types/mdast": { + "@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", "dev": true, - "dependencies": { + "requires": { "@types/unist": "*" } }, - "node_modules/@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "node_modules/@types/mocha": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", - "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "@types/mocha": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", + "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", "dev": true }, - "node_modules/@types/ms": { + "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", "dev": true }, - "node_modules/@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", "dev": true }, - "node_modules/@types/normalize-package-data": { + "@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/object-inspect": { + "@types/object-inspect": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@types/object-inspect/-/object-inspect-1.8.1.tgz", "integrity": "sha512-0JTdf3CGV0oWzE6Wa40Ayv2e2GhpP3pEJMcrlM74vBSJPuuNkVwfDnl0SZxyFCXETcB4oKA/MpTVfuYSMOelBg==", "dev": true }, - "node_modules/@types/puppeteer": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.5.tgz", - "integrity": "sha512-lxCjpDEY+DZ66+W3x5Af4oHnEmUXt0HuaRzkBGE2UZiZEp/V1d3StpLPlmNVu/ea091bdNmVPl44lu8Wy/0ZCA==", + "@types/puppeteer": { + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.7.tgz", + "integrity": "sha512-JdGWZZYL0vKapXF4oQTC5hLVNfOgdPrqeZ1BiQnGk5cB7HeE91EWUiTdVSdQPobRN8rIcdffjiOgCYJ/S8QrnQ==", "dev": true, - "dependencies": { + "requires": { "@types/node": "*" } }, - "node_modules/@types/recursive-readdir": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.0.tgz", - "integrity": "sha512-HGk753KRu2N4mWduovY4BLjYq4jTOL29gV2OfGdGxHcPSWGFkC5RRIdk+VTs5XmYd7MVAD+JwKrcb5+5Y7FOCg==", + "@types/recursive-readdir": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.1.tgz", + "integrity": "sha512-Xd+Ptc4/F2ueInqy5yK2FI5FxtwwbX2+VZpcg+9oYsFJVen8qQKGapCr+Bi5wQtHU1cTXT8s+07lo/nKPgu8Gg==", "dev": true, - "dependencies": { + "requires": { "@types/node": "*" } }, - "node_modules/@types/responselike": { + "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", "dev": true, - "dependencies": { + "requires": { "@types/node": "*" } }, - "node_modules/@types/stack-utils": { + "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, - "node_modules/@types/stream-buffers": { + "@types/stream-buffers": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.4.tgz", "integrity": "sha512-qU/K1tb2yUdhXkLIATzsIPwbtX6BpZk0l3dPW6xqWyhfzzM1ECaQ/8faEnu3CNraLiQ9LHyQQPBGp7N9Fbs25w==", "dev": true, - "dependencies": { + "requires": { "@types/node": "*" } }, - "node_modules/@types/supports-color": { + "@types/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", "dev": true }, - "node_modules/@types/through": { + "@types/through": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", "dev": true, - "dependencies": { + "requires": { "@types/node": "*" } }, - "node_modules/@types/tmp": { + "@types/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==", "dev": true }, - "node_modules/@types/ua-parser-js": { + "@types/ua-parser-js": { "version": "0.7.36", "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", "dev": true }, - "node_modules/@types/unist": { + "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, - "node_modules/@types/vinyl": { + "@types/vinyl": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", "dev": true, - "dependencies": { + "requires": { "@types/expect": "^1.20.4", "@types/node": "*" } }, - "node_modules/@types/which": { + "@types/which": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", "dev": true }, - "node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "@types/yargs": { + "version": "15.0.14", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", + "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", "dev": true, - "dependencies": { + "requires": { "@types/yargs-parser": "*" } }, - "node_modules/@types/yargs-parser": { + "@types/yargs-parser": { "version": "21.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, - "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, - "dependencies": { + "requires": { "@types/node": "*" } }, - "node_modules/@ungap/promise-all-settled": { + "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "node_modules/@vue/compiler-core": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.38.tgz", - "integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==", + "@videojs/http-streaming": { + "version": "2.14.3", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz", + "integrity": "sha512-2tFwxCaNbcEZzQugWf8EERwNMyNtspfHnvxRGRABQs09W/5SqmkWFuGWfUAm4wQKlXGfdPyAJ1338ASl459xAA==", "dev": true, - "optional": true, - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.38", - "estree-walker": "^2.0.2", - "source-map": "^0.6.1" + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "3.0.5", + "aes-decrypter": "3.1.3", + "global": "^4.4.0", + "m3u8-parser": "4.7.1", + "mpd-parser": "0.21.1", + "mux.js": "6.0.1", + "video.js": "^6 || ^7" } }, - "node_modules/@vue/compiler-core/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" + "requires": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" } }, - "node_modules/@vue/compiler-dom": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz", - "integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==", + "@videojs/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==", "dev": true, - "optional": true, - "dependencies": { - "@vue/compiler-core": "3.2.38", - "@vue/shared": "3.2.38" + "requires": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" } }, - "node_modules/@vue/compiler-sfc": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz", - "integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==", + "@vue/compiler-core": { + "version": "3.2.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", + "integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==", "dev": true, "optional": true, - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.38", - "@vue/compiler-dom": "3.2.38", - "@vue/compiler-ssr": "3.2.38", - "@vue/reactivity-transform": "3.2.38", - "@vue/shared": "3.2.38", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz", - "integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==", - "dev": true, - "optional": true, - "dependencies": { - "@vue/compiler-dom": "3.2.38", - "@vue/shared": "3.2.38" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz", - "integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==", - "dev": true, - "optional": true, - "dependencies": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.38", - "@vue/shared": "3.2.38", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "node_modules/@vue/shared": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.38.tgz", - "integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==", - "dev": true, - "optional": true - }, - "node_modules/@wdio/browserstack-service": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-7.16.16.tgz", - "integrity": "sha512-q4wUh/j0MR2SwhTkmIFif2DaXgH5yzdgOer6G/fac2n81zLCSpQHWO5aQ9T0An9CAd4L2A+t3dmChpBJPkHWSw==", - "dev": true, - "dependencies": { - "@types/node": "^17.0.4", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "browserstack-local": "^1.4.5", - "got": "^11.0.2", - "webdriverio": "7.16.16" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/cli": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.16.16.tgz", - "integrity": "sha512-Wz/e5zm1UNHB9RAIsJIM7ioDzVllUwTvhVWOrI7HR/53GmO/cIvAVjpnlglizJNgK8WlbnM/cKNVIXxqxrnFmw==", - "dev": true, - "dependencies": { - "@types/ejs": "^3.0.5", - "@types/fs-extra": "^9.0.4", - "@types/inquirer": "^8.1.2", - "@types/lodash.flattendeep": "^4.4.6", - "@types/lodash.pickby": "^4.6.6", - "@types/lodash.union": "^4.6.6", - "@types/node": "^17.0.4", - "@types/recursive-readdir": "^2.2.0", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "async-exit-hook": "^2.0.1", - "chalk": "^4.0.0", - "chokidar": "^3.0.0", - "cli-spinners": "^2.1.0", - "ejs": "^3.0.1", - "fs-extra": "^10.0.0", - "inquirer": "8.1.5", - "lodash.flattendeep": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.union": "^4.6.0", - "mkdirp": "^1.0.4", - "recursive-readdir": "^2.2.2", - "webdriverio": "7.16.16", - "yargs": "^17.0.0", - "yarn-install": "^1.0.0" - }, - "bin": { - "wdio": "bin/wdio.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wdio/cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/cli/node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wdio/concise-reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-7.16.14.tgz", - "integrity": "sha512-CR+9+skJ3mXPIdRo0AnIJTJHOArrWdKlXTnyZ/DD6M9VrNk5aiTWQyphT/IeHV5+fxjHlMNIf/KgIhj1ewschQ==", - "dev": true, - "dependencies": { - "@wdio/reporter": "7.16.14", - "@wdio/types": "7.16.14", - "chalk": "^4.0.0", - "pretty-ms": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/concise-reporter/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/concise-reporter/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/concise-reporter/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wdio/concise-reporter/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/concise-reporter/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/config": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.16.16.tgz", - "integrity": "sha512-K/ObPuo6Da2liz++OKOIfbdpFwI7UWiFcBylfJkCYbweuXCoW1aUqlKI6rmKPwCH9Uqr/RHWu6p8eo0zWe6xVA==", - "dev": true, - "dependencies": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/local-runner": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.16.16.tgz", - "integrity": "sha512-AJaOyM842PWgMffrrXyHJjouVseLHoiL5U1sw2VVproi3ORWHbltl1AMnreU/lrGu9L0CVKHYT1pxu5UbSOCxQ==", - "dev": true, - "dependencies": { - "@types/stream-buffers": "^3.0.3", - "@wdio/logger": "7.16.0", - "@wdio/repl": "7.16.14", - "@wdio/runner": "7.16.16", - "@wdio/types": "7.16.14", - "async-exit-hook": "^2.0.1", - "split2": "^4.0.0", - "stream-buffers": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/logger": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.16.0.tgz", - "integrity": "sha512-/6lOGb2Iow5eSsy7RJOl1kCwsP4eMlG+/QKro5zUJsuyNJSQXf2ejhpkzyKWLgQbHu83WX6cM1014AZuLkzoQg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/logger/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/logger/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/logger/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wdio/logger/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/logger/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/mocha-framework": { - "version": "7.16.15", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.16.15.tgz", - "integrity": "sha512-XRya85/RYPZk4MZ7Cvl3oudTdrOo+RyO8b5Ff+dH8hD3GBCACaWgW9AjbsyhvbSTdUlF0gNLPdqOCsxV5XyM3w==", - "dev": true, - "dependencies": { - "@types/mocha": "^9.0.0", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "expect-webdriverio": "^3.0.0", - "mocha": "^9.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@wdio/mocha-framework/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/mocha-framework/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/mocha": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", - "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.2.0", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/@wdio/mocha-framework/node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/protocols": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.16.7.tgz", - "integrity": "sha512-Wv40pNQcLiPzQ3o98Mv4A8T1EBQ6k4khglz/e2r16CTm+F3DDYh8eLMAsU5cgnmuwwDKX1EyOiFwieykBn5MCg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/repl": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.14.tgz", - "integrity": "sha512-Ezih0Y+lsGkKv3H3U56hdWgZiQGA3VaAYguSLd9+g1xbQq+zMKqSmfqECD9bAy+OgCCiVTRstES6lHZxJVPhAg==", - "dev": true, - "dependencies": { - "@wdio/utils": "7.16.14" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.16.14.tgz", - "integrity": "sha512-e/I2oGfqjx9+zI4NT/garqxm7Afnos1EcrGSNu75WmP3PNJt4i+9DKkROu4PM6XWcpUB4v2UF7Mv/NrL3TU9aA==", - "dev": true, - "dependencies": { - "@types/diff": "^5.0.0", - "@types/node": "^17.0.4", - "@types/object-inspect": "^1.8.0", - "@types/supports-color": "^8.1.0", - "@types/tmp": "^0.2.0", - "@wdio/types": "7.16.14", - "diff": "^5.0.0", - "fs-extra": "^10.0.0", - "object-inspect": "^1.10.3", - "supports-color": "8.1.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/runner": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.16.16.tgz", - "integrity": "sha512-Tt2ja6GukGPq1m98WP26yOWUGwzK1y7gPTLy6rKlamz3mOBC7koL0T9+iqcFREquUe4CMy2jWp1lqvPlwMbu7g==", - "dev": true, - "dependencies": { - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "deepmerge": "^4.0.0", - "gaze": "^1.1.2", - "webdriver": "7.16.16", - "webdriverio": "7.16.16" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/spec-reporter": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.1.tgz", - "integrity": "sha512-qnZkn3VcyBPtcorUtpyCFE8v5ubyWmR7mFETXNzyriHyvjvk+NeFCWaFcIehpXYXiAmNpAwyfnZoIY6tkKQixQ==", - "dev": true, - "dependencies": { - "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.19.1", - "@wdio/types": "7.19.1", - "chalk": "^4.0.0", - "easy-table": "^1.1.1", - "pretty-ms": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.1.tgz", - "integrity": "sha512-sWmBBV4dPCZkGk9Qq0m35T/vHGen0N10nH4osQcVP3IZJqpo2eLIH4w+X6EUbjZ2GdgOA2bLMMzb1bl9JqnGPg==", - "dev": true, - "dependencies": { - "@types/diff": "^5.0.0", - "@types/node": "^17.0.4", - "@types/object-inspect": "^1.8.0", - "@types/supports-color": "^8.1.0", - "@types/tmp": "^0.2.0", - "@wdio/types": "7.19.1", - "diff": "^5.0.0", - "fs-extra": "^10.0.0", - "object-inspect": "^1.10.3", - "supports-color": "8.1.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/spec-reporter/node_modules/@wdio/types": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.1.tgz", - "integrity": "sha512-mOodKlmvYxpj8P5BhjggEGpXuiRSlsyn2ClG8QqJ3lfXgOtOVEzFNfv/Ai7TkHr+lHDQNXLjllCjSqoCHhwlqg==", - "dev": true, - "dependencies": { - "@types/node": "^17.0.4", - "got": "^11.8.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/spec-reporter/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/spec-reporter/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wdio/spec-reporter/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wdio/spec-reporter/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/spec-reporter/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@wdio/sync": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-7.16.16.tgz", - "integrity": "sha512-MbVFAteaAOxHLKkMiMzOnh1hzINAK2U41GDIfy1yaPumcw1pNuJIhWrBYxprNMlqt8srk++wqQWgj5XpFjCL6g==", - "dev": true, - "dependencies": { - "@types/fibers": "^3.1.0", - "@types/puppeteer": "^5.4.0", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "fibers": "^5.0.0", - "webdriverio": "7.16.16" - }, - "engines": { - "node": ">=12.0.0 <16" - } - }, - "node_modules/@wdio/types": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.16.14.tgz", - "integrity": "sha512-AyNI9iBSos9xWBmiFAF3sBs6AJXO/55VppU/eeF4HRdbZMtMarnvMuahM+jlUrA3vJSmDW+ufelG0MT//6vrnw==", - "dev": true, - "dependencies": { - "@types/node": "^17.0.4", - "got": "^11.8.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/utils": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.16.14.tgz", - "integrity": "sha512-wwin8nVpIlhmXJkq6GJw9aDDzgLOJKgXTcEua0T2sdXjoW78u5Ly/GZrFXTjMGhacFvoZfitTrjyfyy4CxMVvw==", - "dev": true, - "dependencies": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "p-iteration": "^1.1.8" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "node_modules/agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", - "dev": true, - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/archiver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", - "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", - "dev": true, - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.0", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver/node_modules/async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", - "dev": true - }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", - "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, - "node_modules/array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", - "dev": true, - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", - "dev": true, - "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "node_modules/async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", - "dev": true, - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "node_modules/babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "node_modules/babel-code-frame/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "dependencies": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "node_modules/babel-core/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/babel-core/node_modules/json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/babel-core/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/babel-core/node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "dependencies": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "node_modules/babel-generator/node_modules/detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-generator/node_modules/jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "node_modules/babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - } - }, - "node_modules/babel-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/babel-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dependencies": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - } - }, - "node_modules/babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "dependencies": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "node_modules/babel-register/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "node_modules/babel-register/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/babel-register/node_modules/source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "node_modules/babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "dependencies": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-traverse/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/babel-traverse/node_modules/globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-traverse/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "node_modules/babel-types/node_modules/to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true, - "bin": { - "babylon": "bin/babylon.js" - } - }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", - "dev": true, - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", - "dev": true, - "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=", - "dev": true - }, - "node_modules/body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", - "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } - }, - "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/body/node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", - "dev": true - }, - "node_modules/body/node_modules/raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", - "dev": true, - "dependencies": { - "bytes": "1", - "string_decoder": "0.10" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/body/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", - "dependencies": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - } - }, - "node_modules/browserstack-local": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.9.tgz", - "integrity": "sha512-V+q8HQwRQFr9nd32xR66ZZ3VDWa3Kct4IMMudhKgcuD7cWrvvFARZOibx71II+Rf7P5nMQpWWxl9z/3p927nbg==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^4.0.0", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - } - }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/browserstacktunnel-wrapper": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.4.tgz", - "integrity": "sha512-GCV599FUUxNOCFl3WgPnfc5dcqq9XTmMXoxWpqkvmk0R9TOIoqmjENNU6LY6DtgIL6WfBVbg/jmWtnM5K6UYSg==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1", - "unzipper": "^0.9.3" - }, - "engines": { - "node": ">= 0.10.20" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buffer-indexof-polyfill": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", - "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", - "dev": true, - "engines": { - "node": ">=0.2.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cac": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", - "integrity": "sha1-bSTO7Dcu/lybeYgIvH9JtHJCpO8=", - "dev": true, - "dependencies": { - "camelcase-keys": "^3.0.0", - "chalk": "^1.1.3", - "indent-string": "^3.0.0", - "minimist": "^1.2.0", - "read-pkg-up": "^1.0.1", - "suffix": "^0.1.0", - "text-table": "^0.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cac/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/camelcase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", - "integrity": "sha1-/AxsNgNj9zd+N5O5oWvM8QcMHKQ=", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cac/node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001390", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", - "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==" - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true - }, - "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", - "dev": true, - "dependencies": { - "traverse": ">=0.3.0 <0.4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "dev": true - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-launcher": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.0.tgz", - "integrity": "sha512-ZQqX5kb9H0+jy1OqLnWampfocrtSZaGl7Ny3F9GRha85o4odbL8x55paUzh51UC7cEmZ5obp3H2Mm70uC2PpRA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/chrome-launcher/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", - "dev": true, - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", - "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", - "dev": true - }, - "node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dev": true, - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "dev": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/concat-with-sourcemaps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect-livereload": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", - "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "node_modules/core-js": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", - "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==" - }, - "node_modules/core-js-compat": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", - "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dependencies": { - "browserslist": "^4.19.1", - "semver": "7.0.0" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-js-pure": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", - "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - }, - "bin": { - "coveralls": "bin/coveralls.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/crc-32": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", - "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", - "dev": true, - "dependencies": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.3.1" - }, - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/criteo-direct-rsa-validate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", - "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" - }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.7" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" - }, - "node_modules/css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" - } - }, - "node_modules/css-shorthand-properties": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", - "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", - "dev": true - }, - "node_modules/css-value": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", - "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", - "dev": true - }, - "node_modules/css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "dependencies": { - "array-find-index": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/date-format": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.4.tgz", - "integrity": "sha512-/jyf4rhB17ge328HJuJjAcmRtCsGd+NDeAtahRBTaK6vSPR6MO5HlrAit3Nn7dVjaa6sowW0WXt8yQtLyZQFRg==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", - "dev": true, - "optional": true - }, - "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/debug-fabulous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", - "dev": true, - "dependencies": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" - } - }, - "node_modules/debug-fabulous/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", - "dev": true, - "dependencies": { - "character-entities": "^2.0.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deep-equal": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", - "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "es-get-iterator": "^1.1.1", - "get-intrinsic": "^1.0.1", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.1.1", - "isarray": "^2.0.5", - "object-is": "^1.1.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.3", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/devtools": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.16.tgz", - "integrity": "sha512-M0kzkuSgfEhpqIis3gdtWsNjn/HQ+vRAmEzDnbYx/7FfjFxhSv1d+rOOT20pvd60soItMYpsOova1igACEGkGQ==", - "dev": true, - "dependencies": { - "@types/node": "^17.0.4", - "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "chrome-launcher": "^0.15.0", - "edge-paths": "^2.1.0", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.973690", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.973690.tgz", - "integrity": "sha512-myh3hSFp0YWa2GED11PmbLhV4dv9RdO7YUz27XJrbQLnP5bMbZL6dfOOILTHO57yH0kX5GfuOZBsg/4NamfPvQ==", - "dev": true - }, - "node_modules/devtools/node_modules/ua-parser-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.2.tgz", - "integrity": "sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/devtools/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/doctrine-temporary-fork": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", - "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/documentation": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", - "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.18.10", - "@babel/generator": "^7.18.10", - "@babel/parser": "^7.18.11", - "@babel/traverse": "^7.18.11", - "@babel/types": "^7.18.10", - "chalk": "^5.0.1", - "chokidar": "^3.5.3", - "diff": "^5.1.0", - "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^12.0.0", - "github-slugger": "1.4.0", - "glob": "^8.0.3", - "globals-docs": "^2.4.1", - "highlight.js": "^11.6.0", - "ini": "^3.0.0", - "js-yaml": "^4.1.0", - "konan": "^2.1.1", - "lodash": "^4.17.21", - "mdast-util-find-and-replace": "^2.2.1", - "mdast-util-inject": "^1.1.0", - "micromark-util-character": "^1.1.0", - "parse-filepath": "^1.0.2", - "pify": "^6.0.0", - "read-pkg-up": "^9.1.0", - "remark": "^14.0.2", - "remark-gfm": "^3.0.1", - "remark-html": "^15.0.1", - "remark-reference-links": "^6.0.1", - "remark-toc": "^8.0.1", - "resolve": "^1.22.1", - "strip-json-comments": "^5.0.0", - "unist-builder": "^3.0.0", - "unist-util-visit": "^4.1.0", - "vfile": "^5.3.4", - "vfile-reporter": "^7.0.4", - "vfile-sort": "^3.0.0", - "yargs": "^17.5.1" - }, - "bin": { - "documentation": "bin/documentation.js" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@vue/compiler-sfc": "^3.2.37", - "vue-template-compiler": "^2.7.8" - } - }, - "node_modules/documentation/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/documentation/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/documentation/node_modules/chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - } - }, - "node_modules/documentation/node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/documentation/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/documentation/node_modules/ini": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", - "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/documentation/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/documentation/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/documentation/node_modules/strip-json-comments": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", - "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/documentation/node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/dset": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", - "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==", - "engines": { - "node": ">=4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "node_modules/duplexify/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/easy-table": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", - "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "wcwidth": "^1.0.1" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/edge-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", - "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", - "dev": true, - "dependencies": { - "@types/which": "^1.3.2", - "which": "^2.0.2" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", - "dev": true, - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.243", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.243.tgz", - "integrity": "sha512-BgLD2gBX43OSXwlT01oYRRD5NIB4n3okTRxkzEAC6G0SZG4TTlyrWMjbOo0fajCwqwpRtMHXQNMjtRN6qpNtfw==" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", - "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", - "dev": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "dev": true, - "dependencies": { - "@socket.io/base64-arraybuffer": "~1.0.2" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", - "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "dev": true, - "dependencies": { - "string-template": "~0.2.1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", - "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-get-iterator": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", - "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.0", - "has-symbols": "^1.0.1", - "is-arguments": "^1.1.0", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es5-ext": { - "version": "0.10.57", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.57.tgz", - "integrity": "sha512-L7cCNoPwTkAp7IBHxrKLsh7NKiVFkcdxlP9vbVw9QUvb7gF0Mz9bEBN0WY9xqdTjGF907EMT/iG013vnbqwu1Q==", - "dev": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es5-shim": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.5.tgz", - "integrity": "sha512-vfQ4UAai8szn0sAubCy97xnZ4sJVDD1gt/Grn736hg8D7540wemIb1YPrYZSTqlM2H69EQX1or4HU/tSwRTI3w==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", - "dev": true - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "dependencies": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=0.12.0" - }, - "optionalDependencies": { - "source-map": "~0.2.0" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/eslint-config-standard": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", - "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", - "dev": true - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "find-up": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", - "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.2", - "has": "^1.0.3", - "is-core-module": "^2.8.0", - "is-glob": "^4.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.5", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.12.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint-plugin-prebid": { - "resolved": "plugins/eslint", - "link": true - }, - "node_modules/eslint-plugin-promise": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", - "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", - "dev": true, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/eslint-plugin-standard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", - "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", - "dev": true - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "optional": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/event-stream/node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect-webdriverio": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-3.1.4.tgz", - "integrity": "sha512-65FTS3bmxcIp0cq6fLb/72TrCQXBCpwPLC7SwMWdpPlLq461mXcK1BPKJJjnIC587aXSKD+3E4hvnlCtwDmXfg==", - "dev": true, - "dependencies": { - "expect": "^27.0.2", - "jest-matcher-utils": "^27.0.2" - } - }, - "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.19.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.9.7", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "node_modules/ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dev": true, - "dependencies": { - "type": "^2.5.0" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==", - "dev": true - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/faker": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", - "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", - "dev": true - }, - "node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fibers": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.1.tgz", - "integrity": "sha512-VMC7Frt87Oo0AOJ6EcPFbi+tZmkQ4tD85aatwyWL6I9cYMJmm2e+pXUJsfGZ36U7MffXtjou2XIiWJMtHriErw==", - "dev": true, - "dependencies": { - "detect-libc": "^1.0.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "node_modules/foreachasync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", - "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=", - "dev": true - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/fork-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", - "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", - "dev": true - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fs-mkdirp-stream/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/fs.extra": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", - "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", - "dev": true, - "dependencies": { - "fs-extra": "~0.6.1", - "mkdirp": "~0.3.5", - "walk": "^2.3.9" - }, - "engines": { - "node": "*" - } - }, - "node_modules/fs.extra/node_modules/fs-extra": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", - "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", - "dev": true, - "dependencies": { - "jsonfile": "~1.0.1", - "mkdirp": "0.3.x", - "ncp": "~0.4.2", - "rimraf": "~2.2.0" - } - }, - "node_modules/fs.extra/node_modules/jsonfile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", - "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", - "dev": true - }, - "node_modules/fs.extra/node_modules/mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", - "dev": true - }, - "node_modules/fs.extra/node_modules/rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/fstream/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/fstream/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/fun-hooks": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.10.tgz", - "integrity": "sha512-7xBjdT+oMYOPWgwFxNiNzF4ubeUvim4zs1DnQqSSGyxu8UD7AW/6Z0iFsVRwuVSIZKUks2en2VHHotmNfj3ipw==", - "dependencies": { - "typescript-tuple": "^2.2.1" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/git-up": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", - "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", - "dev": true, - "dependencies": { - "is-ssh": "^1.4.0", - "parse-url": "^7.0.2" - } - }, - "node_modules/git-url-parse": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", - "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", - "dev": true, - "dependencies": { - "git-up": "^6.0.0" - } - }, - "node_modules/github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", - "dev": true - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-stream/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-stream/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-watcher/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/glob-watcher/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/glob-watcher/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/glob-watcher/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-watcher/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/glob-watcher/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/globals-docs": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz", - "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", - "dev": true - }, - "node_modules/globule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz", - "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-clean": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", - "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", - "dev": true, - "dependencies": { - "gulp-util": "^2.2.14", - "rimraf": "^2.2.8", - "through2": "^0.4.2" - }, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/gulp-clean/node_modules/ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "dependencies": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "dev": true, - "dependencies": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "node_modules/gulp-clean/node_modules/dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "dependencies": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - }, - "bin": { - "dateformat": "bin/cli.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/gulp-clean/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/gulp-util": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", - "dev": true, - "dependencies": { - "chalk": "^0.5.0", - "dateformat": "^1.0.7-1.2.3", - "lodash._reinterpolate": "^2.4.1", - "lodash.template": "^2.4.1", - "minimist": "^0.2.0", - "multipipe": "^0.1.0", - "through2": "^0.5.0", - "vinyl": "^0.2.1" - }, - "engines": { - "node": ">= 0.9" - } - }, - "node_modules/gulp-clean/node_modules/gulp-util/node_modules/through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", - "dev": true, - "dependencies": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" - } - }, - "node_modules/gulp-clean/node_modules/gulp-util/node_modules/xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/gulp-clean/node_modules/has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "dev": true, - "dependencies": { - "ansi-regex": "^0.2.0" - }, - "bin": { - "has-ansi": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/gulp-clean/node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/lodash._reinterpolate": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", - "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", - "dev": true - }, - "node_modules/gulp-clean/node_modules/lodash.defaults": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", - "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", - "dev": true, - "dependencies": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "node_modules/gulp-clean/node_modules/lodash.template": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", - "dev": true, - "dependencies": { - "lodash._escapestringchar": "~2.4.1", - "lodash._reinterpolate": "~2.4.1", - "lodash.defaults": "~2.4.1", - "lodash.escape": "~2.4.1", - "lodash.keys": "~2.4.1", - "lodash.templatesettings": "~2.4.1", - "lodash.values": "~2.4.1" - } - }, - "node_modules/gulp-clean/node_modules/lodash.templatesettings": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", - "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "~2.4.1", - "lodash.escape": "~2.4.1" - } - }, - "node_modules/gulp-clean/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "dependencies": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/meow/node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/gulp-clean/node_modules/minimist": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", - "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", - "dev": true - }, - "node_modules/gulp-clean/node_modules/object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - }, - "node_modules/gulp-clean/node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/gulp-clean/node_modules/redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "dependencies": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/gulp-clean/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/gulp-clean/node_modules/strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "dev": true, - "dependencies": { - "ansi-regex": "^0.2.1" - }, - "bin": { - "strip-ansi": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "dependencies": { - "get-stdin": "^4.0.1" - }, - "bin": { - "strip-indent": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true, - "bin": { - "supports-color": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/through2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", - "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", - "dev": true, - "dependencies": { - "readable-stream": "~1.0.17", - "xtend": "~2.1.1" - } - }, - "node_modules/gulp-clean/node_modules/trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-clean/node_modules/vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", - "dev": true, - "dependencies": { - "clone-stats": "~0.0.1" - }, - "engines": { - "node": ">= 0.9" - } - }, - "node_modules/gulp-clean/node_modules/xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "dev": true, - "dependencies": { - "object-keys": "~0.4.0" - }, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-cli/node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/gulp-cli/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "node_modules/gulp-cli/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "node_modules/gulp-cli/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "node_modules/gulp-cli/node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "node_modules/gulp-cli/node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, - "node_modules/gulp-cli/node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } - }, - "node_modules/gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", - "dev": true, - "dependencies": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-concat/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-connect": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/gulp-connect/-/gulp-connect-5.7.0.tgz", - "integrity": "sha512-8tRcC6wgXMLakpPw9M7GRJIhxkYdgZsXwn7n56BA2bQYGLR9NOPhMzx7js+qYDy6vhNkbApGKURjAw1FjY4pNA==", - "dev": true, - "dependencies": { - "ansi-colors": "^2.0.5", - "connect": "^3.6.6", - "connect-livereload": "^0.6.0", - "fancy-log": "^1.3.2", - "map-stream": "^0.0.7", - "send": "^0.16.2", - "serve-index": "^1.9.1", - "serve-static": "^1.13.2", - "tiny-lr": "^1.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-connect/node_modules/ansi-colors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-2.0.5.tgz", - "integrity": "sha512-yAdfUZ+c2wetVNIFsNRn44THW+Lty6S5TwMpUfLA/UaGhiXbBv/F8E60/1hMLd0cnF/CDoWH8vzVaI5bAcHCjw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/gulp-connect/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/gulp-connect/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/gulp-connect/node_modules/mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true, - "bin": { - "mime": "cli.js" - } - }, - "node_modules/gulp-connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/gulp-connect/node_modules/send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-connect/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/gulp-eslint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", - "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", - "dev": true, - "dependencies": { - "eslint": "^6.0.0", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1" - } - }, - "node_modules/gulp-eslint/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-eslint/node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/gulp-eslint/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/gulp-eslint/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - } - }, - "node_modules/gulp-eslint/node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-eslint/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/gulp-eslint/node_modules/eslint/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-eslint/node_modules/espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "dependencies": { - "flat-cache": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-eslint/node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-eslint/node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gulp-eslint/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-eslint/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/gulp-eslint/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-eslint/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-eslint/node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true, - "engines": { - "node": ">=6.5.0" - } - }, - "node_modules/gulp-eslint/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/gulp-eslint/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-eslint/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-eslint/node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-eslint/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-eslint/node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/table/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-eslint/node_modules/table/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-eslint/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-eslint/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-eslint/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/gulp-if": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", - "integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==", - "dev": true, - "dependencies": { - "gulp-match": "^1.1.0", - "ternary-stream": "^3.0.0", - "through2": "^3.0.1" - } - }, - "node_modules/gulp-if/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/gulp-js-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gulp-js-escape/-/gulp-js-escape-1.0.1.tgz", - "integrity": "sha1-HNRF+9AJ4Np2lZoDp/SbNWav+Gg=", - "dev": true, - "dependencies": { - "through2": "^0.6.3" - } - }, - "node_modules/gulp-js-escape/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/gulp-js-escape/node_modules/readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/gulp-js-escape/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/gulp-js-escape/node_modules/through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true, - "dependencies": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, - "node_modules/gulp-match": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.1.0.tgz", - "integrity": "sha512-DlyVxa1Gj24DitY2OjEsS+X6tDpretuxD6wTfhXE/Rw2hweqc1f6D/XtsJmoiCwLWfXgR87W9ozEityPCVzGtQ==", - "dev": true, - "dependencies": { - "minimatch": "^3.0.3" - } - }, - "node_modules/gulp-replace": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", - "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", - "dev": true, - "dependencies": { - "@types/node": "^14.14.41", - "@types/vinyl": "^2.0.4", - "istextorbinary": "^3.0.0", - "replacestream": "^4.0.3", - "yargs-parser": ">=5.0.0-security.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gulp-replace/node_modules/@types/node": { - "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", - "dev": true - }, - "node_modules/gulp-shell": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/gulp-shell/-/gulp-shell-0.8.0.tgz", - "integrity": "sha512-wHNCgmqbWkk1c6Gc2dOL5SprcoeujQdeepICwfQRo91DIylTE7a794VEE+leq3cE2YDoiS5ulvRfKVIEMazcTQ==", - "dev": true, - "dependencies": { - "chalk": "^3.0.0", - "fancy-log": "^1.3.3", - "lodash.template": "^4.5.0", - "plugin-error": "^1.0.1", - "through2": "^3.0.1", - "tslib": "^1.10.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/gulp-shell/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-shell/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-shell/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/gulp-shell/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/gulp-shell/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gulp-shell/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/gulp-sourcemaps": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-3.0.0.tgz", - "integrity": "sha512-RqvUckJkuYqy4VaIH60RMal4ZtG0IbQ6PXMNkNsshEGJ9cldUPRb/YCgboYae+CLAs1HQNb4ADTKCx65HInquQ==", - "dev": true, - "dependencies": { - "@gulp-sourcemaps/identity-map": "^2.0.1", - "@gulp-sourcemaps/map-sources": "^1.0.0", - "acorn": "^6.4.1", - "convert-source-map": "^1.0.0", - "css": "^3.0.0", - "debug-fabulous": "^1.0.0", - "detect-newline": "^2.0.0", - "graceful-fs": "^4.0.0", - "source-map": "^0.6.0", - "strip-bom-string": "^1.0.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gulp-sourcemaps/node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/gulp-sourcemaps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-sourcemaps/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-terser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/gulp-terser/-/gulp-terser-2.1.0.tgz", - "integrity": "sha512-lQ3+JUdHDVISAlUIUSZ/G9Dz/rBQHxOiYDQ70IVWFQeh4b33TC1MCIU+K18w07PS3rq/CVc34aQO4SUbdaNMPQ==", - "dev": true, - "dependencies": { - "plugin-error": "^1.0.1", - "terser": "^5.9.0", - "through2": "^4.0.2", - "vinyl-sourcemaps-apply": "^0.2.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "dependencies": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/gulp-util/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/gulp-util/node_modules/clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "node_modules/gulp-util/node_modules/lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "dependencies": { - "lodash._root": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "dependencies": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "dependencies": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "node_modules/gulp-util/node_modules/object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-util/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/gulp-util/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/gulp-util/node_modules/vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "dependencies": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - }, - "engines": { - "node": ">= 0.9" - } - }, - "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "dependencies": { - "glogg": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/har-validator/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hast-util-is-element": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", - "integrity": "sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0" - } - }, - "node_modules/hast-util-sanitize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.0.0.tgz", - "integrity": "sha512-pw56+69jq+QSr/coADNvWTmBPDy+XsmwaF5KnUys4/wM1jt/fZdl7GPxhXXXYdXnz3Gj3qMkbUCH2uKjvX0MgQ==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0" - } - }, - "node_modules/hast-util-to-html": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz", - "integrity": "sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-is-element": "^2.0.0", - "hast-util-whitespace": "^2.0.0", - "html-void-elements": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.2", - "unist-util-is": "^5.0.0" - } - }, - "node_modules/hast-util-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", - "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", - "dev": true - }, - "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/highlight.js": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", - "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/html-void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", - "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", - "dev": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, - "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", - "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, - "dependencies": { - "agent-base": "5", - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/inquirer": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.5.tgz", - "integrity": "sha512-G6/9xUqmt/r+UvufSyrPpt84NYwhKZ9jLsgMbQzlx804XErNupor8WQdBnBRrXmBfTPpuwf1sV+ss2ovjgdXIg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.2.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", - "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", - "dev": true - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-number/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", - "dev": true - }, - "node_modules/is-set": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", - "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", - "dev": true - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "dev": true - }, - "node_modules/is-ssh": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", - "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", - "dev": true, - "dependencies": { - "protocols": "^2.0.1" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", - "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - } - }, - "node_modules/is-weakset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", - "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isbinaryfile": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", - "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "node_modules/istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "dependencies": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "istanbul": "lib/cli.js" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", - "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/istanbul/node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/istanbul/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "node_modules/istanbul/node_modules/supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "dependencies": { - "has-flag": "^1.0.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/istanbul/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/istextorbinary": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", - "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", - "dev": true, - "dependencies": { - "binaryextensions": "^2.2.0", - "textextensions": "^3.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", - "dev": true, - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "node_modules/jake/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/just-clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", - "integrity": "sha1-v7P672WqEqMWBYcSlFwyb9jwFDQ=" - }, - "node_modules/just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "dev": true - }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "node_modules/karma": { - "version": "6.3.17", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz", - "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==", - "dev": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.2.0", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/karma-babel-preprocessor": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/karma-babel-preprocessor/-/karma-babel-preprocessor-8.0.2.tgz", - "integrity": "sha512-6ZUnHwaK2EyhgxbgeSJW6n6WZUYSEdekHIV/qDUnPgMkVzQBHEvd07d2mTL5AQjV8uTUgH6XslhaPrp+fHWH2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/karma-browserstack-launcher": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.4.0.tgz", - "integrity": "sha512-bUQK84U+euDfOUfEjcF4IareySMOBNRLrrl9q6cttIe8f011Ir6olLITTYMOJDcGY58wiFIdhPHSPd9Pi6+NfQ==", - "dev": true, - "dependencies": { - "browserstack": "~1.5.1", - "browserstacktunnel-wrapper": "~2.0.2", - "q": "~1.5.0" - } - }, - "node_modules/karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", - "dev": true - }, - "node_modules/karma-chrome-launcher": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", - "dev": true, - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/karma-coverage": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", - "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.1", - "istanbul-reports": "^3.0.5", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/karma-coverage-istanbul-reporter": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", - "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^3.0.2", - "minimatch": "^3.0.4" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma-es5-shim": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/karma-es5-shim/-/karma-es5-shim-0.0.4.tgz", - "integrity": "sha1-zdADM8znfC5M4D46yT8vjs0fuVI=", - "dev": true, - "dependencies": { - "es5-shim": "^4.0.5" - } - }, - "node_modules/karma-firefox-launcher": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", - "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", - "dev": true, - "dependencies": { - "is-wsl": "^2.2.0", - "which": "^2.0.1" - } - }, - "node_modules/karma-ie-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", - "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", - "dev": true, - "dependencies": { - "lodash": "^4.6.1" - } - }, - "node_modules/karma-mocha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", - "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.3" - } - }, - "node_modules/karma-mocha-reporter": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", - "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", - "dev": true, - "dependencies": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" - } - }, - "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/karma-opera-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-opera-launcher/-/karma-opera-launcher-1.0.0.tgz", - "integrity": "sha1-+lFihTGh0L6EstjcDX7iCfyP+Ro=", - "dev": true - }, - "node_modules/karma-safari-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", - "integrity": "sha1-lpgqLMR9BmquccVTursoMZEVos4=", - "dev": true - }, - "node_modules/karma-script-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-script-launcher/-/karma-script-launcher-1.0.0.tgz", - "integrity": "sha1-zQF8TeXvCeWp2nkydhdhCN1LVC0=", - "dev": true - }, - "node_modules/karma-sinon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/karma-sinon/-/karma-sinon-1.0.5.tgz", - "integrity": "sha1-TjRD8oMP3s/2JNN0cWPxIX2qKpo=", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/karma-sourcemap-loader": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz", - "integrity": "sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/karma-spec-reporter": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", - "integrity": "sha1-LpxyB+pyZ3EmAln4K+y1QyCeRAo=", - "dev": true, - "dependencies": { - "colors": "^1.1.2" - } - }, - "node_modules/karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "webpack-merge": "^4.1.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/karma/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/karma/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/keyv": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.1.1.tgz", - "integrity": "sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/konan": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", - "integrity": "sha512-7ZhYV84UzJ0PR/RJnnsMZcAbn+kLasJhVNWsu8ZyVEJYRpGA5XESQ9d/7zOa08U0Ou4cmB++hMNY/3OSV9KIbg==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.10.5", - "@babel/traverse": "^7.10.5" - } - }, - "node_modules/ky": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.29.0.tgz", - "integrity": "sha512-01TBSOqlHmLfcQhHseugGHLxPtU03OyZWaLDWt5MfzCkijG6xWFvAQPhKVn0cR2MMjYvBP9keQ8A3+rQEhLO5g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", - "dev": true, - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", - "dev": true, - "bin": { - "lcov-parse": "bin/cli.js" - } - }, - "node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", - "dev": true, - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/liftoff/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lighthouse-logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.3.0.tgz", - "integrity": "sha512-BbqAKApLb9ywUli+0a+PcV04SyJ/N1q/8qgCNe6U97KbPCS1BTksEuHFLYdvc8DltuhfxIUBqDZsC0bBGtl3lA==", - "dev": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/lighthouse-logger/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/listenercount": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", - "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=", - "dev": true - }, - "node_modules/live-connect-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.4.0.tgz", - "integrity": "sha512-MSBLKfnXoxH+pqwji/Mf8yZu3VZMq4tnNfwMw7NTWN5a+TBM6f0RWgwui1YMA3nHmMhX/nzxxsso0SkyKcF0fA==", - "dependencies": { - "tiny-hashes": "1.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/livereload-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", - "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", - "dev": true - }, - "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "node_modules/lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "node_modules/lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "node_modules/lodash._escapehtmlchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", - "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", - "dev": true, - "dependencies": { - "lodash._htmlescapes": "~2.4.1" - } - }, - "node_modules/lodash._escapestringchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", - "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", - "dev": true - }, - "node_modules/lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "node_modules/lodash._htmlescapes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", - "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", - "dev": true - }, - "node_modules/lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "node_modules/lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", - "dev": true - }, - "node_modules/lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", - "dev": true - }, - "node_modules/lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "node_modules/lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "node_modules/lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "node_modules/lodash._reunescapedhtml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", - "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", - "dev": true, - "dependencies": { - "lodash._htmlescapes": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "node_modules/lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "dev": true, - "dependencies": { - "lodash._objecttypes": "~2.4.1" - } - }, - "node_modules/lodash.clone": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", - "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", - "dev": true - }, - "node_modules/lodash.escape": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", - "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", - "dev": true, - "dependencies": { - "lodash._escapehtmlchar": "~2.4.1", - "lodash._reunescapedhtml": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "node_modules/lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "node_modules/lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "node_modules/lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", - "dev": true, - "dependencies": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - } - }, - "node_modules/lodash.keys/node_modules/lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", - "dev": true, - "dependencies": { - "lodash._objecttypes": "~2.4.1" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.pickby": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", - "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=", - "dev": true - }, - "node_modules/lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "node_modules/lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", - "dev": true - }, - "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "node_modules/lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dev": true, - "dependencies": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", - "dev": true - }, - "node_modules/lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", - "dev": true, - "dependencies": { - "lodash.keys": "~2.4.1" - } - }, - "node_modules/lodash.zip": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", - "integrity": "sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=", - "dev": true - }, - "node_modules/log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true, - "engines": { - "node": ">=0.8.6" - } - }, - "node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log4js": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.2.tgz", - "integrity": "sha512-k80cggS2sZQLBwllpT1p06GtfvzMmSdUCkW96f0Hj83rKGJDAu2vZjt9B9ag2vx8Zz1IXzxoLgqvRJCdMKybGg==", - "dev": true, - "dependencies": { - "date-format": "^4.0.4", - "debug": "^4.3.3", - "flatted": "^3.2.5", - "rfdc": "^1.3.0", - "streamroller": "^3.0.4" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/loglevel": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", - "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/loglevel-plugin-prefix": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", - "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", - "dev": true - }, - "node_modules/lolex": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", - "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", - "dev": true - }, - "node_modules/longest-streak": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz", - "integrity": "sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "dependencies": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, - "dependencies": { - "es5-ext": "~0.10.2" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "optional": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", - "dev": true - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/markdown-table": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz", - "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==", - "dev": true - }, - "node_modules/marky": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.4.tgz", - "integrity": "sha512-zd2/GiSn6U3/jeFVZ0J9CA1LzQ8RfIVvXkb/U0swFHF/zT+dVohTAWjmo2DcIuofmIIIROlwTbd+shSeXmxr0w==", - "dev": true - }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", - "dev": true, - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mdast-util-definitions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz", - "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "unist-util-visit": "^4.0.0" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz", - "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.0.0" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", - "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "micromark": "^3.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-decode-string": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "unist-util-stringify-position": "^3.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true - }, - "node_modules/mdast-util-gfm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz", - "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==", - "dev": true, - "dependencies": { - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-gfm-autolink-literal": "^1.0.0", - "mdast-util-gfm-footnote": "^1.0.0", - "mdast-util-gfm-strikethrough": "^1.0.0", - "mdast-util-gfm-table": "^1.0.0", - "mdast-util-gfm-task-list-item": "^1.0.0", - "mdast-util-to-markdown": "^1.0.0" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz", - "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "ccount": "^2.0.0", - "mdast-util-find-and-replace": "^2.0.0", - "micromark-util-character": "^1.0.0" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz", - "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.3.0", - "micromark-util-normalize-identifier": "^1.0.0" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.1.tgz", - "integrity": "sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.3.0" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.4.tgz", - "integrity": "sha512-aEuoPwZyP4iIMkf2cLWXxx3EQ6Bmh2yKy9MVCg4i6Sd3cX80dcLEfXO/V4ul3pGH9czBK4kp+FAl+ZHmSUt9/w==", - "dev": true, - "dependencies": { - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "mdast-util-to-markdown": "^1.3.0" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz", - "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.3.0" - } - }, - "node_modules/mdast-util-inject": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", - "integrity": "sha1-2wa4tYW+lZotzS+H9HK6m3VvNnU=", - "dev": true, - "dependencies": { - "mdast-util-to-string": "^1.0.0" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.1.tgz", - "integrity": "sha512-dyindR2P7qOqXO1hQirZeGtVbiX7xlNQbw7gGaAwN4A1dh4+X8xU/JyYmRoyB8Fu1uPXzp7mlL5QwW7k+knvgA==", - "dev": true, - "dependencies": { - "@types/hast": "^2.0.0", - "@types/mdast": "^3.0.0", - "@types/mdurl": "^1.0.0", - "mdast-util-definitions": "^5.0.0", - "mdurl": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "trim-lines": "^3.0.0", - "unist-builder": "^3.0.0", - "unist-util-generated": "^2.0.0", - "unist-util-position": "^4.0.0", - "unist-util-visit": "^4.0.0" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz", - "integrity": "sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "longest-streak": "^3.0.0", - "mdast-util-to-string": "^3.0.0", - "micromark-util-decode-string": "^1.0.0", - "unist-util-visit": "^4.0.0", - "zwitch": "^2.0.0" - } - }, - "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true - }, - "node_modules/mdast-util-to-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", - "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", - "dev": true - }, - "node_modules/mdast-util-toc": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.0.tgz", - "integrity": "sha512-0PuqZELXZl4ms1sF7Lqigrqik4Ll3UhbI+jdTrfw7pZ9QPawgl7LD4GQ8MkU7bT/EwiVqChNTbifa2jLLKo76A==", - "dev": true, - "dependencies": { - "@types/extend": "^3.0.0", - "@types/github-slugger": "^1.0.0", - "@types/mdast": "^3.0.0", - "extend": "^3.0.0", - "github-slugger": "^1.0.0", - "mdast-util-to-string": "^3.1.0", - "unist-util-is": "^5.0.0", - "unist-util-visit": "^3.0.0" - } - }, - "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", - "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", - "dev": true - }, - "node_modules/mdast-util-toc/node_modules/unist-util-visit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", - "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^4.0.0" - } - }, - "node_modules/mdast-util-toc/node_modules/unist-util-visit-parents": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", - "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", - "es6-weak-map": "^2.0.3", - "event-emitter": "^0.3.5", - "is-promise": "^2.2.2", - "lru-queue": "^0.1.0", - "next-tick": "^1.1.0", - "timers-ext": "^0.1.7" - } - }, - "node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromark": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", - "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", - "dev": true, - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "micromark-core-commonmark": "^1.0.1", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", - "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", - "dev": true, - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-factory-destination": "^1.0.0", - "micromark-factory-label": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-factory-title": "^1.0.0", - "micromark-factory-whitespace": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-html-tag-name": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-subtokenize": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.1", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", - "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", - "dev": true, - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^1.0.0", - "micromark-extension-gfm-footnote": "^1.0.0", - "micromark-extension-gfm-strikethrough": "^1.0.0", - "micromark-extension-gfm-table": "^1.0.0", - "micromark-extension-gfm-tagfilter": "^1.0.0", - "micromark-extension-gfm-task-list-item": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", - "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", - "dev": true, - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz", - "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==", - "dev": true, - "dependencies": { - "micromark-core-commonmark": "^1.0.0", - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-normalize-identifier": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz", - "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==", - "dev": true, - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-classify-character": "^1.0.0", - "micromark-util-resolve-all": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", - "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", - "dev": true, - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz", - "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==", - "dev": true, - "dependencies": { - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz", - "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==", - "dev": true, - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-destination": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", - "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", - "dev": true, - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", - "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", - "dev": true, - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", - "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", - "dev": true, - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", - "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", - "dev": true, - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", - "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", - "dev": true, - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", - "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", - "dev": true, - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", - "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", - "dev": true, - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", - "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", - "dev": true, - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", - "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", - "dev": true, - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", - "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", - "dev": true, - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", - "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", - "dev": true, - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^1.0.0", - "micromark-util-decode-numeric-character-reference": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", - "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", - "dev": true - }, - "node_modules/micromark-util-html-tag-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", - "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", - "dev": true - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", - "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", - "dev": true, - "dependencies": { - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", - "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", - "dev": true, - "dependencies": { - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", - "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", - "dev": true, - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-encode": "^1.0.0", - "micromark-util-symbol": "^1.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", - "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", - "dev": true, - "dependencies": { - "micromark-util-chunked": "^1.0.0", - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0", - "uvu": "^0.5.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", - "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", - "dev": true - }, - "node_modules/micromark-util-types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", - "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", - "dev": true - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dependencies": { - "mime-db": "1.51.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mocha/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "node_modules/mocha/node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "dev": true, - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", - "integrity": "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "dependencies": { - "duplexer2": "0.0.2" - } - }, - "node_modules/multipipe/node_modules/duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "dependencies": { - "readable-stream": "~1.1.9" - } - }, - "node_modules/multipipe/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/multipipe/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/multipipe/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true, - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "optional": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/ncp": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", - "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=", - "dev": true, - "bin": { - "ncp": "bin/ncp" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "dependencies": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/nise/node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "node_modules/nise/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/nise/node_modules/lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" - }, - "node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", - "dev": true - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "dev": true, - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.values": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", - "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dev": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/opn/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-iteration": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.8.tgz", - "integrity": "sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parse-ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", - "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", - "dev": true, - "dependencies": { - "protocols": "^2.0.0" - } - }, - "node_modules/parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", - "dev": true, - "dependencies": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, - "dependencies": { - "through": "~2.3" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/pify": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", - "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/plugin-error/node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "dependencies": { - "ansi-wrap": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", - "dev": true, - "optional": true, - "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pretty-ms": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", - "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", - "dev": true, - "dependencies": { - "parse-ms": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/printj": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", - "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", - "dev": true, - "bin": { - "printj": "bin/printj.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/property-information": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", - "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", - "dev": true - }, - "node_modules/protocols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", - "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", - "dev": true - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "node_modules/ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/puppeteer-core": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.5.1.tgz", - "integrity": "sha512-dobVqWjV34ilyfQHR3BBnCYaekBYTi5MgegEYBRYd3s3uFy8jUpZEEWbaFjG9ETm+LGzR5Lmr0aF6LLuHtiuCg==", - "dev": true, - "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.3", - "devtools-protocol": "0.0.969999", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.5.0" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/puppeteer-core/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.969999", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.969999.tgz", - "integrity": "sha512-6GfzuDWU0OFAuOvBokXpXPLxjOJ5DZ157Ue3sGQQM3LgAamb8m0R0ruSfN0DDu+XG5XJgT50i6zZ/0o8RglreQ==", - "dev": true - }, - "node_modules/puppeteer-core/node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/query-selector-shadow-dom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.0.tgz", - "integrity": "sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==", - "dev": true - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" - }, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", - "dev": true, - "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dev": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.1.1.tgz", - "integrity": "sha512-vJXaRMJgRVD3+cUZs3Mncj2mxpt5mP0EmNOsxRSZRMlbqjvxzDEOIUWXGmavo0ZC9+tNZCBLQ66reA11nbpHZg==", - "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dev": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/read-pkg-up/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/readdir-glob": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "dev": true, - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", - "dev": true, - "dependencies": { - "minimatch": "3.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/recursive-readdir/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" - }, - "node_modules/regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/remark": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.2.tgz", - "integrity": "sha512-A3ARm2V4BgiRXaUo5K0dRvJ1lbogrbXnhkJRmD0yw092/Yl0kOCZt1k9ZeElEwkZsWGsMumz6qL5MfNJH9nOBA==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "remark-parse": "^10.0.0", - "remark-stringify": "^10.0.0", - "unified": "^10.0.0" - } - }, - "node_modules/remark-gfm": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", - "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-gfm": "^2.0.0", - "micromark-extension-gfm": "^2.0.0", - "unified": "^10.0.0" - } - }, - "node_modules/remark-html": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.1.tgz", - "integrity": "sha512-7ta5UPRqj8nP0GhGMYUAghZ/DRno7dgq7alcW90A7+9pgJsXzGJlFgwF8HOP1b1tMgT3WwbeANN+CaTimMfyNQ==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "hast-util-sanitize": "^4.0.0", - "hast-util-to-html": "^8.0.0", - "mdast-util-to-hast": "^12.0.0", - "unified": "^10.0.0" - } - }, - "node_modules/remark-parse": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", - "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-from-markdown": "^1.0.0", - "unified": "^10.0.0" - } - }, - "node_modules/remark-reference-links": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-6.0.1.tgz", - "integrity": "sha512-34wY2C6HXSuKVTRtyJJwefkUD8zBOZOSHFZ4aSTnU2F656gr9WeuQ2dL6IJDK3NPd2F6xKF2t4XXcQY9MygAXg==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "unified": "^10.0.0", - "unist-util-visit": "^4.0.0" - } - }, - "node_modules/remark-stringify": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.2.tgz", - "integrity": "sha512-6wV3pvbPvHkbNnWB0wdDvVFHOe1hBRAx1Q/5g/EpH4RppAII6J8Gnwe7VbHuXaoKIF6LAg6ExTel/+kNqSQ7lw==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-to-markdown": "^1.0.0", - "unified": "^10.0.0" - } - }, - "node_modules/remark-toc": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-8.0.1.tgz", - "integrity": "sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==", - "dev": true, - "dependencies": { - "@types/mdast": "^3.0.0", - "mdast-util-toc": "^6.0.0", - "unified": "^10.0.0" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-buffer/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", - "dev": true, - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-bom-stream/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "dependencies": { - "is-finite": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/replacestream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", - "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", - "dev": true, - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "node_modules/responselike": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", - "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - } - }, - "node_modules/resq": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/resq/-/resq-1.10.2.tgz", - "integrity": "sha512-HmgVS3j+FLrEDBTDYysPdPVF9/hioDMJ/otOiQDKqk77YfZeeLOj0qi34yObumcud1gBpk+wpBTEg4kMicD++A==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^2.0.1" - } - }, - "node_modules/resq/node_modules/fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rgb2hex": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", - "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/rxjs": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", - "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", - "dev": true, - "dependencies": { - "sver-compat": "^1.5.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sinon": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", - "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", - "dev": true, - "dependencies": { - "@sinonjs/formatio": "^2.0.0", - "diff": "^3.1.0", - "lodash.get": "^4.4.2", - "lolex": "^2.2.0", - "nise": "^1.2.0", - "supports-color": "^5.1.0", - "type-detect": "^4.0.5" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/sinon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sirv": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", - "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", - "dev": true, - "dependencies": { - "@polka/url": "^1.0.0-next.20", - "mrmime": "^1.0.0", - "totalist": "^1.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/socket.io": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", - "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "dev": true - }, - "node_modules/socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", - "dev": true, - "dependencies": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true, - "optional": true - }, - "node_modules/space-separated-tokens": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", - "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", - "dev": true - }, - "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", - "dev": true, - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-buffers": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", - "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/streamroller": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.4.tgz", - "integrity": "sha512-GI9NzeD+D88UFuIlJkKNDH/IsuR+qIN7Qh8EsmhoRZr9bQoehTraRgwtLUkZbpcAw+hLPfHOypmppz8YyGK68w==", - "dev": true, - "dependencies": { - "date-format": "^4.0.4", - "debug": "^4.3.3", - "fs-extra": "^10.0.1" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", - "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", - "dev": true, - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/suffix": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", - "integrity": "sha1-zFgjFkag7xEC95R47zqSSP2chC8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", - "dev": true, - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", - "dev": true, - "dependencies": { - "rimraf": "~2.5.2" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/temp-fs/node_modules/rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", - "dev": true, - "dependencies": { - "glob": "^7.0.5" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ternary-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz", - "integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==", - "dev": true, - "dependencies": { - "duplexify": "^4.1.1", - "fork-stream": "^0.0.4", - "merge-stream": "^2.0.0", - "through2": "^3.0.1" - } - }, - "node_modules/ternary-stream/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", - "dev": true, - "dependencies": { - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/textextensions": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", - "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/through2-filter/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "node_modules/tiny-hashes": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tiny-hashes/-/tiny-hashes-1.0.1.tgz", - "integrity": "sha512-knIN5zj4fl7kW4EBU5sLP20DWUvi/rVouvJezV0UAym2DkQaqm365Nyc8F3QEiOvunNDMxR8UhcXd1d5g+Wg1g==" - }, - "node_modules/tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", - "dev": true, - "dependencies": { - "body": "^5.1.0", - "debug": "^3.1.0", - "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" - } - }, - "node_modules/tiny-lr/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/to-regex-range/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", - "dev": true, - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/to-through/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/totalist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", - "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", - "dev": true - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true - }, - "node_modules/trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", - "dev": true - }, - "node_modules/tsconfig-paths": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.13.0.tgz", - "integrity": "sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "node_modules/typescript-compare": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", - "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==", - "dependencies": { - "typescript-logic": "^0.0.0" - } - }, - "node_modules/typescript-logic": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz", - "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==" - }, - "node_modules/typescript-tuple": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz", - "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==", - "dependencies": { - "typescript-compare": "^0.0.2" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/uglify-js": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.2.tgz", - "integrity": "sha512-peeoTk3hSwYdoc9nrdiEJk+gx1ALCtTjdYuKSXMTDqq7n1W7dHPqWDdSi+BPL0ni2YMeHD7hKUSdbj3TZauY2A==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker/node_modules/fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", - "dev": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unified": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", - "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "bail": "^2.0.0", - "extend": "^3.0.0", - "is-buffer": "^2.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^5.0.0" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/unist-builder": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz", - "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0" - } - }, - "node_modules/unist-util-generated": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz", - "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==", - "dev": true - }, - "node_modules/unist-util-is": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", - "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", - "dev": true - }, - "node_modules/unist-util-position": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz", - "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", - "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0" - } - }, - "node_modules/unist-util-visit": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz", - "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0", - "unist-util-visit-parents": "^5.1.1" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz", - "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-is": "^5.0.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/unzipper": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.9.15.tgz", - "integrity": "sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", - "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/uvu": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", - "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", - "dev": true, - "dependencies": { - "dequal": "^2.0.0", - "diff": "^5.0.0", - "kleur": "^4.0.3", - "sade": "^1.7.3" - }, - "bin": { - "uvu": "bin.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/vfile": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz", - "integrity": "sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-message": "^3.0.0" - } - }, - "node_modules/vfile-message": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", - "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", - "dev": true, - "dependencies": { - "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^3.0.0" - } - }, - "node_modules/vfile-reporter": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.4.tgz", - "integrity": "sha512-4cWalUnLrEnbeUQ+hARG5YZtaHieVK3Jp4iG5HslttkVl+MHunSGNAIrODOTLbtjWsNZJRMCkL66AhvZAYuJ9A==", - "dev": true, - "dependencies": { - "@types/supports-color": "^8.0.0", - "string-width": "^5.0.0", - "supports-color": "^9.0.0", - "unist-util-stringify-position": "^3.0.0", - "vfile-sort": "^3.0.0", - "vfile-statistics": "^2.0.0" - } - }, - "node_modules/vfile-reporter/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/vfile-reporter/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/vfile-reporter/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/vfile-reporter/node_modules/supports-color": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", - "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/vfile-sort": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.0.tgz", - "integrity": "sha512-fJNctnuMi3l4ikTVcKpxTbzHeCgvDhnI44amA3NVDvA6rTC6oKCFpCVyT5n2fFMr3ebfr+WVQZedOCd73rzSxg==", - "dev": true, - "dependencies": { - "vfile-message": "^3.0.0" - } - }, - "node_modules/vfile-statistics": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.0.tgz", - "integrity": "sha512-foOWtcnJhKN9M2+20AOTlWi2dxNfAoeNIoxD5GXcO182UJyId4QrXa41fWrgcfV3FWTjdEDy3I4cpLVcQscIMA==", - "dev": true, - "dependencies": { - "vfile-message": "^3.0.0" - } - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-fs/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", - "dev": true, - "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", - "dev": true, - "dependencies": { - "source-map": "^0.5.1" - } - }, - "node_modules/vinyl/node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vue-template-compiler": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz", - "integrity": "sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==", - "dev": true, - "optional": true, - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/vue-template-compiler/node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "optional": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/walk": { - "version": "2.3.15", - "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.15.tgz", - "integrity": "sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg==", - "dev": true, - "dependencies": { - "foreachasync": "^3.0.0" - } - }, - "node_modules/watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webdriver": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.16.tgz", - "integrity": "sha512-x8UoG9k/P8KDrfSh1pOyNevt9tns3zexoMxp9cKnyA/7HYSErhZYTLGlgxscAXLtQG41cMH/Ba/oBmOx7Hgd8w==", - "dev": true, - "dependencies": { - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "got": "^11.0.2", - "ky": "^0.29.0", - "lodash.merge": "^4.6.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/webdriverio": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.16.tgz", - "integrity": "sha512-caPaEWyuD3Qoa7YkW4xCCQA4v9Pa9wmhFGPvNZh3ERtjMCNi8L/XXOdkekWNZmFh3tY0kFguBj7+fAwSY7HAGw==", - "dev": true, - "dependencies": { - "@types/aria-query": "^5.0.0", - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/repl": "7.16.14", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "archiver": "^5.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.16.16", - "devtools-protocol": "^0.0.973690", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^5.0.0", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.16.16" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/webdriverio/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/webdriverio/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", - "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", - "dev": true, - "dependencies": { - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "chalk": "^4.1.0", - "commander": "^7.2.0", - "gzip-size": "^6.0.0", - "lodash": "^4.17.20", - "opener": "^1.5.2", - "sirv": "^1.0.7", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/webpack-manifest-plugin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz", - "integrity": "sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==", - "dev": true, - "dependencies": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", - "dev": true, - "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webpack-stream/-/webpack-stream-7.0.0.tgz", - "integrity": "sha512-XoAQTHyCaYMo6TS7Atv1HYhtmBgKiVLONJbzLBl2V3eibXQ2IT/MCRM841RW/r3vToKD5ivrTJFWgd/ghoxoRg==", - "dev": true, - "dependencies": { - "fancy-log": "^1.3.3", - "lodash.clone": "^4.3.2", - "lodash.some": "^4.2.2", - "memory-fs": "^0.5.0", - "plugin-error": "^1.0.1", - "supports-color": "^8.1.1", - "through": "^2.3.8", - "vinyl": "^2.2.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/webpack/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack/node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true - }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "node_modules/which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "dependencies": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", - "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.7" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/write/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz", - "integrity": "sha1-BU3oth8i7v23IHBZ6u+da4P7kxo=", - "dev": true - }, - "node_modules/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yarn-install": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yarn-install/-/yarn-install-1.0.0.tgz", - "integrity": "sha1-V/RQULgu/VcYKzlzxUqgXLXSUjA=", - "dev": true, - "dependencies": { - "cac": "^3.0.3", - "chalk": "^1.1.3", - "cross-spawn": "^4.0.2" - }, - "bin": { - "yarn-install": "bin/yarn-install.js", - "yarn-remove": "bin/yarn-remove.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yarn-install/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "node_modules/yarn-install/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/yarn-install/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/yarn-install/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/yarn-install/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dev": true, - "dependencies": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/zip-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/zwitch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", - "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==", - "dev": true - }, - "plugins/eslint": { - "name": "eslint-plugin-prebid", - "version": "1.0.0", - "dev": true, - "license": "Apache-2.0" - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", - "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.0" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz", - "integrity": "sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==" - }, - "@babel/core": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", - "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/eslint-parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz", - "integrity": "sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA==", - "dev": true, - "requires": { - "eslint-scope": "^5.1.1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "requires": { - "@babel/types": "^7.19.0", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", - "integrity": "sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==", - "requires": { - "@babel/compat-data": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.20.2", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", - "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", - "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^5.0.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", - "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" - } - }, - "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==" - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" - }, - "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "requires": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" - } - }, - "@babel/helpers": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", - "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", - "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==" - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", - "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", - "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", - "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.17.6", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", - "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", - "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", - "requires": { - "@babel/compat-data": "^7.17.0", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.16.7" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", - "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", - "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", - "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "requires": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", - "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", - "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", - "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", - "requires": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", - "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", - "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", - "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", - "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz", - "integrity": "sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg==", - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.9", - "babel-plugin-polyfill-corejs2": "^0.3.1", - "babel-plugin-polyfill-corejs3": "^0.5.2", - "babel-plugin-polyfill-regenerator": "^0.3.1", - "semver": "^6.3.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "requires": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.11", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.8", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", - "semver": "^6.3.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", - "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", - "requires": { - "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" - } - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz", - "integrity": "sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.0", - "@babel/types": "^7.19.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - } - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@gulp-sourcemaps/identity-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", - "integrity": "sha512-Tb+nSISZku+eQ4X1lAkevcQa+jknn/OVUgZ3XCxEKIsLsqYuPoJwJOPQeaOk75X3WPftb29GWY1eqE7GLsXb1Q==", - "dev": true, - "requires": { - "acorn": "^6.4.1", - "normalize-path": "^3.0.0", - "postcss": "^7.0.16", - "source-map": "^0.6.0", - "through2": "^3.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - } - } - }, - "@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", - "dev": true, - "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@polka/url": { - "version": "1.0.0-next.21", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", - "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", - "dev": true - }, - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/formatio": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", - "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", - "dev": true, - "requires": { - "samsam": "1.3.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@socket.io/base64-arraybuffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/aria-query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.0.tgz", - "integrity": "sha512-P+dkdFu0n08PDIvw+9nT9ByQnd+Udc8DaWPb9HKfaPwCvWvQpC5XaMRx2xLWECm9x1VKNps6vEAlirjA6+uNrQ==", - "dev": true - }, - "@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, - "requires": { - "@types/ms": "*" - } - }, - "@types/diff": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.2.tgz", - "integrity": "sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==", - "dev": true - }, - "@types/easy-table": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/easy-table/-/easy-table-0.0.33.tgz", - "integrity": "sha512-/vvqcJPmZUfQwCgemL0/34G7bIQnCuvgls379ygRlcC1FqNqk3n+VZ15dAO51yl6JNDoWd8vsk+kT8zfZ1VZSw==", - "dev": true - }, - "@types/ejs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.0.tgz", - "integrity": "sha512-DCg+Ka+uDQ31lJ/UtEXVlaeV3d6t81gifaVWKJy4MYVVgvJttyX/viREy+If7fz+tK/gVxTGMtyrFPnm4gjrVA==", - "dev": true - }, - "@types/eslint": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", - "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true - }, - "@types/extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.1.tgz", - "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", - "dev": true - }, - "@types/fibers": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.1.tgz", - "integrity": "sha512-yHoUi46uika0snoTpNcVqUSvgbRndaIps4TUCotrXjtc0DHDoPQckmyXEZ2bX3e4mpJmyEW3hRhCwQa/ISCPaA==", - "dev": true - }, - "@types/fs-extra": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/github-slugger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", - "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==", - "dev": true - }, - "@types/hast": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", - "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", - "dev": true, - "requires": { - "@types/unist": "*" - } - }, - "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", - "dev": true - }, - "@types/inquirer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.0.tgz", - "integrity": "sha512-BNoMetRf3gmkpAlV5we+kxyZTle7YibdOntIZbU5pyIfMdcwy784KfeZDAcuyMznkh5OLa17RVXZOGA5LTlkgQ==", - "dev": true, - "requires": { - "@types/through": "*", - "rxjs": "^7.2.0" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "@types/keyv": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", - "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/lodash": { - "version": "4.14.179", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz", - "integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==", - "dev": true - }, - "@types/lodash.flattendeep": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.6.tgz", - "integrity": "sha512-uLm2MaRVlqJSGsMK0RZpP5T3KqReq+9WbYDHCUhBhp98v56hMG/Yht52bsoTSui9xz2mUvQ9NfG3LrNGDL92Ng==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/lodash.pickby": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.6.tgz", - "integrity": "sha512-NFa13XxlMd9eFi0UFZFWIztpMpXhozbijrx3Yb1viYZphT7jyopIFVoIRF4eYMjruWNEG1rnyrRmg/8ej9T8Iw==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/lodash.union": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.6.tgz", - "integrity": "sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", - "dev": true, - "requires": { - "@types/unist": "*" - } - }, - "@types/mdurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", - "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", - "dev": true - }, - "@types/mocha": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", - "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", - "dev": true - }, - "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true - }, - "@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "@types/object-inspect": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/object-inspect/-/object-inspect-1.8.1.tgz", - "integrity": "sha512-0JTdf3CGV0oWzE6Wa40Ayv2e2GhpP3pEJMcrlM74vBSJPuuNkVwfDnl0SZxyFCXETcB4oKA/MpTVfuYSMOelBg==", - "dev": true - }, - "@types/puppeteer": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.5.tgz", - "integrity": "sha512-lxCjpDEY+DZ66+W3x5Af4oHnEmUXt0HuaRzkBGE2UZiZEp/V1d3StpLPlmNVu/ea091bdNmVPl44lu8Wy/0ZCA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/recursive-readdir": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.0.tgz", - "integrity": "sha512-HGk753KRu2N4mWduovY4BLjYq4jTOL29gV2OfGdGxHcPSWGFkC5RRIdk+VTs5XmYd7MVAD+JwKrcb5+5Y7FOCg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/stream-buffers": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.4.tgz", - "integrity": "sha512-qU/K1tb2yUdhXkLIATzsIPwbtX6BpZk0l3dPW6xqWyhfzzM1ECaQ/8faEnu3CNraLiQ9LHyQQPBGp7N9Fbs25w==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", - "dev": true - }, - "@types/through": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==", - "dev": true - }, - "@types/ua-parser-js": { - "version": "0.7.36", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz", - "integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==", - "dev": true - }, - "@types/unist": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", - "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", - "dev": true - }, - "@types/vinyl": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", - "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", - "dev": true, - "requires": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } - }, - "@types/which": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", - "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", - "dev": true - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, "requires": { - "@types/yargs-parser": "*" + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.41", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } } }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "@vue/compiler-dom": { + "version": "3.2.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz", + "integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==", "dev": true, "optional": true, "requires": { - "@types/node": "*" + "@vue/compiler-core": "3.2.41", + "@vue/shared": "3.2.41" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "@vue/compiler-core": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.38.tgz", - "integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==", + "@vue/compiler-sfc": { + "version": "3.2.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz", + "integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==", "dev": true, "optional": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.38", + "@vue/compiler-core": "3.2.41", + "@vue/compiler-dom": "3.2.41", + "@vue/compiler-ssr": "3.2.41", + "@vue/reactivity-transform": "3.2.41", + "@vue/shared": "3.2.41", "estree-walker": "^2.0.2", + "magic-string": "^0.25.7", + "postcss": "^8.1.10", "source-map": "^0.6.1" }, "dependencies": { @@ -22667,126 +1915,263 @@ } } }, - "@vue/compiler-dom": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz", - "integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==", + "@vue/compiler-ssr": { + "version": "3.2.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz", + "integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==", "dev": true, "optional": true, "requires": { - "@vue/compiler-core": "3.2.38", - "@vue/shared": "3.2.38" + "@vue/compiler-dom": "3.2.41", + "@vue/shared": "3.2.41" } }, - "@vue/compiler-sfc": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz", - "integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==", + "@vue/reactivity-transform": { + "version": "3.2.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz", + "integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==", "dev": true, "optional": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.38", - "@vue/compiler-dom": "3.2.38", - "@vue/compiler-ssr": "3.2.38", - "@vue/reactivity-transform": "3.2.38", - "@vue/shared": "3.2.38", + "@vue/compiler-core": "3.2.41", + "@vue/shared": "3.2.41", "estree-walker": "^2.0.2", - "magic-string": "^0.25.7", - "postcss": "^8.1.10", - "source-map": "^0.6.1" + "magic-string": "^0.25.7" + } + }, + "@vue/shared": { + "version": "3.2.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz", + "integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==", + "dev": true, + "optional": true + }, + "@wdio/browserstack-service": { + "version": "7.16.16", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-7.16.16.tgz", + "integrity": "sha512-q4wUh/j0MR2SwhTkmIFif2DaXgH5yzdgOer6G/fac2n81zLCSpQHWO5aQ9T0An9CAd4L2A+t3dmChpBJPkHWSw==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "@wdio/logger": "7.16.0", + "@wdio/types": "7.16.14", + "browserstack-local": "^1.4.5", + "got": "^11.0.2", + "webdriverio": "7.16.16" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "@wdio/config": { + "version": "7.16.16", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.16.16.tgz", + "integrity": "sha512-K/ObPuo6Da2liz++OKOIfbdpFwI7UWiFcBylfJkCYbweuXCoW1aUqlKI6rmKPwCH9Uqr/RHWu6p8eo0zWe6xVA==", + "dev": true, + "requires": { + "@wdio/logger": "7.16.0", + "@wdio/types": "7.16.14", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + } + }, + "@wdio/protocols": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.16.7.tgz", + "integrity": "sha512-Wv40pNQcLiPzQ3o98Mv4A8T1EBQ6k4khglz/e2r16CTm+F3DDYh8eLMAsU5cgnmuwwDKX1EyOiFwieykBn5MCg==", + "dev": true + }, + "@wdio/repl": { + "version": "7.16.14", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.14.tgz", + "integrity": "sha512-Ezih0Y+lsGkKv3H3U56hdWgZiQGA3VaAYguSLd9+g1xbQq+zMKqSmfqECD9bAy+OgCCiVTRstES6lHZxJVPhAg==", + "dev": true, + "requires": { + "@wdio/utils": "7.16.14" + } + }, + "@wdio/utils": { + "version": "7.16.14", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.16.14.tgz", + "integrity": "sha512-wwin8nVpIlhmXJkq6GJw9aDDzgLOJKgXTcEua0T2sdXjoW78u5Ly/GZrFXTjMGhacFvoZfitTrjyfyy4CxMVvw==", + "dev": true, + "requires": { + "@wdio/logger": "7.16.0", + "@wdio/types": "7.16.14", + "p-iteration": "^1.1.8" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "devtools": { + "version": "7.16.16", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.16.tgz", + "integrity": "sha512-M0kzkuSgfEhpqIis3gdtWsNjn/HQ+vRAmEzDnbYx/7FfjFxhSv1d+rOOT20pvd60soItMYpsOova1igACEGkGQ==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "@types/ua-parser-js": "^0.7.33", + "@wdio/config": "7.16.16", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/types": "7.16.14", + "@wdio/utils": "7.16.14", + "chrome-launcher": "^0.15.0", + "edge-paths": "^2.1.0", + "puppeteer-core": "^13.1.3", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^8.0.0" + } + }, + "devtools-protocol": { + "version": "0.0.973690", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.973690.tgz", + "integrity": "sha512-myh3hSFp0YWa2GED11PmbLhV4dv9RdO7YUz27XJrbQLnP5bMbZL6dfOOILTHO57yH0kX5GfuOZBsg/4NamfPvQ==", + "dev": true + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ua-parser-js": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "webdriver": { + "version": "7.16.16", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.16.tgz", + "integrity": "sha512-x8UoG9k/P8KDrfSh1pOyNevt9tns3zexoMxp9cKnyA/7HYSErhZYTLGlgxscAXLtQG41cMH/Ba/oBmOx7Hgd8w==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "@wdio/config": "7.16.16", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/types": "7.16.14", + "@wdio/utils": "7.16.14", + "got": "^11.0.2", + "ky": "^0.29.0", + "lodash.merge": "^4.6.1" + } + }, + "webdriverio": { + "version": "7.16.16", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.16.tgz", + "integrity": "sha512-caPaEWyuD3Qoa7YkW4xCCQA4v9Pa9wmhFGPvNZh3ERtjMCNi8L/XXOdkekWNZmFh3tY0kFguBj7+fAwSY7HAGw==", "dev": true, - "optional": true + "requires": { + "@types/aria-query": "^5.0.0", + "@types/node": "^17.0.4", + "@wdio/config": "7.16.16", + "@wdio/logger": "7.16.0", + "@wdio/protocols": "7.16.7", + "@wdio/repl": "7.16.14", + "@wdio/types": "7.16.14", + "@wdio/utils": "7.16.14", + "archiver": "^5.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "7.16.16", + "devtools-protocol": "^0.0.973690", + "fs-extra": "^10.0.0", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^5.0.0", + "puppeteer-core": "^13.1.3", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.16.16" + } } } }, - "@vue/compiler-ssr": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz", - "integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==", - "dev": true, - "optional": true, - "requires": { - "@vue/compiler-dom": "3.2.38", - "@vue/shared": "3.2.38" - } - }, - "@vue/reactivity-transform": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz", - "integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==", - "dev": true, - "optional": true, - "requires": { - "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.38", - "@vue/shared": "3.2.38", - "estree-walker": "^2.0.2", - "magic-string": "^0.25.7" - } - }, - "@vue/shared": { - "version": "3.2.38", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.38.tgz", - "integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==", - "dev": true, - "optional": true - }, - "@wdio/browserstack-service": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-7.16.16.tgz", - "integrity": "sha512-q4wUh/j0MR2SwhTkmIFif2DaXgH5yzdgOer6G/fac2n81zLCSpQHWO5aQ9T0An9CAd4L2A+t3dmChpBJPkHWSw==", - "dev": true, - "requires": { - "@types/node": "^17.0.4", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "browserstack-local": "^1.4.5", - "got": "^11.0.2", - "webdriverio": "7.16.16" - } - }, "@wdio/cli": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.16.16.tgz", - "integrity": "sha512-Wz/e5zm1UNHB9RAIsJIM7ioDzVllUwTvhVWOrI7HR/53GmO/cIvAVjpnlglizJNgK8WlbnM/cKNVIXxqxrnFmw==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.5.7.tgz", + "integrity": "sha512-nOQJLskrY+UECDd3NxE7oBzb6cDA7e7x02YWQugOlOgnZ4a+PJmkFoSsO8C2uNCpdFngy5rJKGUo5vbtAHEF9Q==", "dev": true, "requires": { "@types/ejs": "^3.0.5", "@types/fs-extra": "^9.0.4", - "@types/inquirer": "^8.1.2", + "@types/inquirer": "^7.3.1", "@types/lodash.flattendeep": "^4.4.6", "@types/lodash.pickby": "^4.6.6", "@types/lodash.union": "^4.6.6", - "@types/node": "^17.0.4", "@types/recursive-readdir": "^2.2.0", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", "async-exit-hook": "^2.0.1", "chalk": "^4.0.0", "chokidar": "^3.0.0", "cli-spinners": "^2.1.0", "ejs": "^3.0.1", "fs-extra": "^10.0.0", - "inquirer": "8.1.5", + "inquirer": "^8.0.0", "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", "mkdirp": "^1.0.4", "recursive-readdir": "^2.2.2", - "webdriverio": "7.16.16", + "webdriverio": "7.5.7", "yargs": "^17.0.0", "yarn-install": "^1.0.0" }, "dependencies": { + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -22796,6 +2181,16 @@ "color-convert": "^2.0.1" } }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -22806,6 +2201,31 @@ "supports-color": "^7.1.0" } }, + "chrome-launcher": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^1.0.5", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^0.5.3", + "rimraf": "^3.0.2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + } + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -22821,6 +2241,65 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "devtools": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", + "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", + "dev": true, + "requires": { + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/protocols": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "chrome-launcher": "^0.13.1", + "edge-paths": "^2.1.0", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + } + }, + "devtools-protocol": { + "version": "0.0.878340", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", + "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "puppeteer-core": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", + "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + } + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -22830,13 +2309,61 @@ "has-flag": "^4.0.0" } }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "webdriverio": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", + "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", + "dev": true, + "requires": { + "@types/aria-query": "^4.2.1", + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/protocols": "7.5.3", + "@wdio/repl": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "archiver": "^5.0.0", + "aria-query": "^4.2.2", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "7.5.7", + "devtools-protocol": "^0.0.878340", + "fs-extra": "^10.0.0", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.5.3" + } + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true + }, "yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", + "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", "dev": true, "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", @@ -22848,17 +2375,26 @@ } }, "@wdio/concise-reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-7.16.14.tgz", - "integrity": "sha512-CR+9+skJ3mXPIdRo0AnIJTJHOArrWdKlXTnyZ/DD6M9VrNk5aiTWQyphT/IeHV5+fxjHlMNIf/KgIhj1ewschQ==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-7.5.7.tgz", + "integrity": "sha512-964i7eQ4sboSla2bdR8714Er82QBgS6u39GmDFX8Izy9Ge38xaE75HuF5S7mnOWGzSojCWgqtwy5k7Rfg6GE3g==", "dev": true, "requires": { - "@wdio/reporter": "7.16.14", - "@wdio/types": "7.16.14", + "@wdio/reporter": "7.5.7", + "@wdio/types": "7.5.3", "chalk": "^4.0.0", "pretty-ms": "^7.0.0" }, "dependencies": { + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -22893,6 +2429,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -22905,31 +2447,175 @@ } }, "@wdio/config": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.16.16.tgz", - "integrity": "sha512-K/ObPuo6Da2liz++OKOIfbdpFwI7UWiFcBylfJkCYbweuXCoW1aUqlKI6rmKPwCH9Uqr/RHWu6p8eo0zWe6xVA==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.5.3.tgz", + "integrity": "sha512-udvVizYoilOxuWj/BmoN6y7ZCd4wPdYNlSfWznrbCezAdaLZ4/pNDOO0WRWx2C4+q1wdkXZV/VuQPUGfL0lEHQ==", "dev": true, "requires": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", + "@wdio/logger": "7.5.3", + "@wdio/types": "7.5.3", "deepmerge": "^4.0.0", "glob": "^7.1.2" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@wdio/local-runner": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.16.16.tgz", - "integrity": "sha512-AJaOyM842PWgMffrrXyHJjouVseLHoiL5U1sw2VVproi3ORWHbltl1AMnreU/lrGu9L0CVKHYT1pxu5UbSOCxQ==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.5.7.tgz", + "integrity": "sha512-aYc0XUV+/e3cg8Fp+CWlC4FbwSSG3mKAv1iuy/+Hwzg2kJE+aa+Rf2p2BQYc7HPRtKNW0bM8o+aCImZLAiPM+A==", "dev": true, "requires": { "@types/stream-buffers": "^3.0.3", - "@wdio/logger": "7.16.0", - "@wdio/repl": "7.16.14", - "@wdio/runner": "7.16.16", - "@wdio/types": "7.16.14", + "@wdio/logger": "7.5.3", + "@wdio/repl": "7.5.3", + "@wdio/runner": "7.5.7", + "@wdio/types": "7.5.3", "async-exit-hook": "^2.0.1", - "split2": "^4.0.0", + "split2": "^3.2.2", "stream-buffers": "^3.0.2" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@wdio/logger": { @@ -22978,6 +2664,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -22990,19 +2682,46 @@ } }, "@wdio/mocha-framework": { - "version": "7.16.15", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.16.15.tgz", - "integrity": "sha512-XRya85/RYPZk4MZ7Cvl3oudTdrOo+RyO8b5Ff+dH8hD3GBCACaWgW9AjbsyhvbSTdUlF0gNLPdqOCsxV5XyM3w==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.5.3.tgz", + "integrity": "sha512-96QCVWsiyZxEgOZP3oTq2B2T7zne5dCdehLa2n4q/BLjk96Rj0jifidJZfd/1+vdNPKX0gWWAzpy98Znn8MVMw==", "dev": true, "requires": { - "@types/mocha": "^9.0.0", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "expect-webdriverio": "^3.0.0", - "mocha": "^9.0.0" + "@types/mocha": "^8.0.0", + "@wdio/logger": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "expect-webdriverio": "^2.0.0", + "mocha": "^8.0.1" }, "dependencies": { + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -23026,17 +2745,33 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + } + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "color-convert": { @@ -23054,6 +2789,21 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -23070,16 +2820,30 @@ "path-exists": "^4.0.0" } }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "dev": true, "requires": { "argparse": "^2.0.1" @@ -23095,13 +2859,12 @@ } }, "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", "dev": true, "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "chalk": "^4.0.0" } }, "minimatch": { @@ -23114,47 +2877,59 @@ } }, "mocha": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.1.tgz", - "integrity": "sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.3", + "chokidar": "3.5.1", + "debug": "4.3.1", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.2.0", - "serialize-javascript": "6.0.0", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.2.0", + "wide-align": "1.1.3", + "workerpool": "6.1.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", "dev": true }, "p-limit": { @@ -23172,111 +2947,353 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "p-limit": "^3.0.2" + "p-limit": "^3.0.2" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + } + } + }, + "@wdio/protocols": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.5.3.tgz", + "integrity": "sha512-lpNaKwxYhDSL6neDtQQYXvzMAw+u4PXx65ryeMEX82mkARgzSZps5Kyrg9ub7X4T17K1NPfnY6UhZEWg6cKJCg==", + "dev": true + }, + "@wdio/repl": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.5.3.tgz", + "integrity": "sha512-jfNJwNoc2nWdnLsFoGHmOJR9zaWfDTBMWM3W1eR5kXIjevD6gAfWsB5ZoA4IdybujCXxdnhlsm4o2jIzp/6f7A==", + "dev": true, + "requires": { + "@wdio/utils": "7.5.3" + } + }, + "@wdio/reporter": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.5.7.tgz", + "integrity": "sha512-9PXqZtCXDtU6UYLNDPu9MZQ8BiABGnRlJTrlbYB3gBfZDibMkJMvwXzPderipBv2+ifDZXmGe3Njf1ao2TkbFA==", + "dev": true, + "requires": { + "@wdio/types": "7.5.3", + "fs-extra": "^10.0.0" + }, + "dependencies": { + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + } + } + }, + "@wdio/runner": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.5.7.tgz", + "integrity": "sha512-RzVXd+xnwK/thkx1/xo9K5iscQ0Ofobgsx5dNVtwLDVMn9V7jCW/WX4dSCPAPaVSqnUCmkcQp3P5AoSBPpCZnQ==", + "dev": true, + "requires": { + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "deepmerge": "^4.0.0", + "gaze": "^1.1.2", + "webdriver": "7.5.3", + "webdriverio": "7.5.7" + }, + "dependencies": { + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chrome-launcher": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^1.0.5", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^0.5.3", + "rimraf": "^3.0.2" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "devtools": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", + "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", + "dev": true, + "requires": { + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/protocols": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "chrome-launcher": "^0.13.1", + "edge-paths": "^2.1.0", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + } + }, + "devtools-protocol": { + "version": "0.0.878340", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", + "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "puppeteer-core": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", + "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "webdriverio": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", + "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", + "dev": true, + "requires": { + "@types/aria-query": "^4.2.1", + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/protocols": "7.5.3", + "@wdio/repl": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "archiver": "^5.0.0", + "aria-query": "^4.2.2", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "7.5.7", + "devtools-protocol": "^0.0.878340", + "fs-extra": "^10.0.0", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.5.3" } }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true } } }, - "@wdio/protocols": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.16.7.tgz", - "integrity": "sha512-Wv40pNQcLiPzQ3o98Mv4A8T1EBQ6k4khglz/e2r16CTm+F3DDYh8eLMAsU5cgnmuwwDKX1EyOiFwieykBn5MCg==", - "dev": true - }, - "@wdio/repl": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.14.tgz", - "integrity": "sha512-Ezih0Y+lsGkKv3H3U56hdWgZiQGA3VaAYguSLd9+g1xbQq+zMKqSmfqECD9bAy+OgCCiVTRstES6lHZxJVPhAg==", - "dev": true, - "requires": { - "@wdio/utils": "7.16.14" - } - }, - "@wdio/reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.16.14.tgz", - "integrity": "sha512-e/I2oGfqjx9+zI4NT/garqxm7Afnos1EcrGSNu75WmP3PNJt4i+9DKkROu4PM6XWcpUB4v2UF7Mv/NrL3TU9aA==", - "dev": true, - "requires": { - "@types/diff": "^5.0.0", - "@types/node": "^17.0.4", - "@types/object-inspect": "^1.8.0", - "@types/supports-color": "^8.1.0", - "@types/tmp": "^0.2.0", - "@wdio/types": "7.16.14", - "diff": "^5.0.0", - "fs-extra": "^10.0.0", - "object-inspect": "^1.10.3", - "supports-color": "8.1.1" - } - }, - "@wdio/runner": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.16.16.tgz", - "integrity": "sha512-Tt2ja6GukGPq1m98WP26yOWUGwzK1y7gPTLy6rKlamz3mOBC7koL0T9+iqcFREquUe4CMy2jWp1lqvPlwMbu7g==", - "dev": true, - "requires": { - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "deepmerge": "^4.0.0", - "gaze": "^1.1.2", - "webdriver": "7.16.16", - "webdriverio": "7.16.16" - } - }, "@wdio/spec-reporter": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.1.tgz", - "integrity": "sha512-qnZkn3VcyBPtcorUtpyCFE8v5ubyWmR7mFETXNzyriHyvjvk+NeFCWaFcIehpXYXiAmNpAwyfnZoIY6tkKQixQ==", + "version": "7.19.7", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.7.tgz", + "integrity": "sha512-BDBZU2EK/GuC9VxtfqPtoW43FmvKxYDsvcDVDi3F7o+9fkcuGSJiWbw1AX251ZzzVQ7YP9ImTitSpdpUKXkilQ==", "dev": true, "requires": { "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.19.1", - "@wdio/types": "7.19.1", + "@wdio/reporter": "7.19.7", + "@wdio/types": "7.19.5", "chalk": "^4.0.0", "easy-table": "^1.1.1", "pretty-ms": "^7.0.0" }, "dependencies": { "@wdio/reporter": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.1.tgz", - "integrity": "sha512-sWmBBV4dPCZkGk9Qq0m35T/vHGen0N10nH4osQcVP3IZJqpo2eLIH4w+X6EUbjZ2GdgOA2bLMMzb1bl9JqnGPg==", + "version": "7.19.7", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.7.tgz", + "integrity": "sha512-Dum19gpfru66FnIq78/4HTuW87B7ceLDp6PJXwQM5kXyN7Gb7zhMgp6FZTM0FCYLyi6U/zXZSvpNUYl77caS6g==", "dev": true, "requires": { "@types/diff": "^5.0.0", @@ -23284,17 +3301,46 @@ "@types/object-inspect": "^1.8.0", "@types/supports-color": "^8.1.0", "@types/tmp": "^0.2.0", - "@wdio/types": "7.19.1", + "@wdio/types": "7.19.5", "diff": "^5.0.0", "fs-extra": "^10.0.0", "object-inspect": "^1.10.3", "supports-color": "8.1.1" + } + }, + "@wdio/types": { + "version": "7.19.5", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.5.tgz", + "integrity": "sha512-S1lC0pmtEO7NVH/2nM1c7NHbkgxLZH3VVG/z6ym3Bbxdtcqi2LMsEvvawMAU/fmhyiIkMsGZCO8vxG9cRw4z4A==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "dependencies": { "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -23302,13 +3348,76 @@ } } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/sync": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-7.5.7.tgz", + "integrity": "sha512-Zu/AYLjwqbFSbaOU1US7ownv3ov8JrtoGHq51JfJ4masefJDXNkHix2cZ0qEgl3IvkkWQ0ewL0G8GTXb3KOemA==", + "dev": true, + "requires": { + "@types/fibers": "^3.1.0", + "@types/puppeteer": "^5.4.0", + "@wdio/logger": "7.5.3", + "@wdio/types": "7.5.3", + "fibers": "^5.0.0", + "webdriverio": "7.5.7" + }, + "dependencies": { + "@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "dev": true + }, + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, "@wdio/types": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.1.tgz", - "integrity": "sha512-mOodKlmvYxpj8P5BhjggEGpXuiRSlsyn2ClG8QqJ3lfXgOtOVEzFNfv/Ai7TkHr+lHDQNXLjllCjSqoCHhwlqg==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", "dev": true, "requires": { - "@types/node": "^17.0.4", "got": "^11.8.1" } }, @@ -23321,6 +3430,16 @@ "color-convert": "^2.0.1" } }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -23331,6 +3450,20 @@ "supports-color": "^7.1.0" } }, + "chrome-launcher": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^1.0.5", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^0.5.3", + "rimraf": "^3.0.2" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -23346,6 +3479,74 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "devtools": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", + "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", + "dev": true, + "requires": { + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/protocols": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "chrome-launcher": "^0.13.1", + "edge-paths": "^2.1.0", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + } + }, + "devtools-protocol": { + "version": "0.0.878340", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", + "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "puppeteer-core": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", + "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.869402", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.869402", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", + "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", + "dev": true + } + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23354,23 +3555,57 @@ "requires": { "has-flag": "^4.0.0" } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "webdriverio": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", + "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", + "dev": true, + "requires": { + "@types/aria-query": "^4.2.1", + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/protocols": "7.5.3", + "@wdio/repl": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", + "archiver": "^5.0.0", + "aria-query": "^4.2.2", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "7.5.7", + "devtools-protocol": "^0.0.878340", + "fs-extra": "^10.0.0", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^9.1.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^8.0.0", + "webdriver": "7.5.3" + } + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true } } }, - "@wdio/sync": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-7.16.16.tgz", - "integrity": "sha512-MbVFAteaAOxHLKkMiMzOnh1hzINAK2U41GDIfy1yaPumcw1pNuJIhWrBYxprNMlqt8srk++wqQWgj5XpFjCL6g==", - "dev": true, - "requires": { - "@types/fibers": "^3.1.0", - "@types/puppeteer": "^5.4.0", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "fibers": "^5.0.0", - "webdriverio": "7.16.16" - } - }, "@wdio/types": { "version": "7.16.14", "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.16.14.tgz", @@ -23382,14 +3617,85 @@ } }, "@wdio/utils": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.16.14.tgz", - "integrity": "sha512-wwin8nVpIlhmXJkq6GJw9aDDzgLOJKgXTcEua0T2sdXjoW78u5Ly/GZrFXTjMGhacFvoZfitTrjyfyy4CxMVvw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.5.3.tgz", + "integrity": "sha512-nlLDKr8v8abLOHCKroBwQkGPdCIxjID2MllgWX23xqkYZylM9RdwPBdL8osQt9m3rq2TxiPAT4OlbzNt2WtN6Q==", "dev": true, "requires": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "p-iteration": "^1.1.8" + "@wdio/logger": "7.5.3", + "@wdio/types": "7.5.3" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@webassemblyjs/ast": { @@ -23538,6 +3844,12 @@ "@xtuc/long": "4.2.2" } }, + "@xmldom/xmldom": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.8.tgz", + "integrity": "sha512-PrJx38EfpitFhwmILRl37jAdBlsww6AZ6rRVK4QS7T7RHLhX7mSs647sTmgr9GIxe3qjXdesmomEgbgaokrVFg==", + "dev": true + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -23577,12 +3889,33 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "aes-decrypter": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz", + "integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0", + "pkcs7": "^1.0.4" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, "ajv": { "version": "6.12.3", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", @@ -23595,6 +3928,12 @@ "uri-js": "^4.2.2" } }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -23603,11 +3942,20 @@ "optional": true }, "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -23626,6 +3974,15 @@ "ansi-wrap": "0.1.0" } }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -23666,13 +4023,13 @@ } }, "archiver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", - "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", + "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", "dev": true, "requires": { "archiver-utils": "^2.1.0", - "async": "^3.2.0", + "async": "^3.2.3", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.0.0", @@ -23681,9 +4038,9 @@ }, "dependencies": { "async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true }, "readable-stream": { @@ -23733,16 +4090,31 @@ } }, "aria-query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", - "integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==", - "dev": true + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } }, "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "dependencies": { + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "dev": true + } + } }, "arr-filter": { "version": "1.1.2", @@ -23769,9 +4141,9 @@ } }, "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", "dev": true }, "array-differ": { @@ -23786,12 +4158,6 @@ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -23804,14 +4170,14 @@ "dev": true }, "array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", "get-intrinsic": "^1.1.1", "is-string": "^1.0.7" } @@ -23881,14 +4247,15 @@ "dev": true }, "array.prototype.flat": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", - "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.19.0" + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" } }, "asn1": { @@ -24111,12 +4478,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true } } }, @@ -24136,15 +4497,6 @@ "trim-right": "^1.0.1" }, "dependencies": { - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "jsesc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", @@ -24164,37 +4516,15 @@ } }, "babel-loader": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", - "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", + "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", "dev": true, "requires": { "find-cache-dir": "^3.3.1", - "loader-utils": "^1.4.0", + "loader-utils": "^2.0.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } } }, "babel-messages": { @@ -24206,14 +4536,6 @@ "babel-runtime": "^6.22.0" } }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - } - }, "babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -24225,48 +4547,33 @@ "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - } } }, "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", "semver": "^6.1.1" } }, "babel-plugin-polyfill-corejs3": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", - "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.21.0" + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1" + "@babel/helper-define-polyfill-provider": "^0.3.3" } }, "babel-register": { @@ -24298,15 +4605,6 @@ "requires": { "minimist": "^1.2.6" } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } } } }, @@ -24325,6 +4623,12 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true } } }, @@ -24481,6 +4785,14 @@ "dev": true, "requires": { "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "batch": { @@ -24615,20 +4927,22 @@ } }, "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -24642,7 +4956,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -24672,14 +4986,14 @@ "dev": true }, "browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "requires": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" + "update-browserslist-db": "^1.0.9" } }, "browserstack": { @@ -24722,21 +5036,22 @@ } }, "browserstack-local": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.9.tgz", - "integrity": "sha512-V+q8HQwRQFr9nd32xR66ZZ3VDWa3Kct4IMMudhKgcuD7cWrvvFARZOibx71II+Rf7P5nMQpWWxl9z/3p927nbg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", + "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", "dev": true, "requires": { - "https-proxy-agent": "^4.0.0", + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", "is-running": "^2.1.0", "ps-tree": "=1.2.0", "temp-fs": "^0.9.9" } }, "browserstacktunnel-wrapper": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.4.tgz", - "integrity": "sha512-GCV599FUUxNOCFl3WgPnfc5dcqq9XTmMXoxWpqkvmk0R9TOIoqmjENNU6LY6DtgIL6WfBVbg/jmWtnM5K6UYSg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.5.tgz", + "integrity": "sha512-oociT3nl+FhQnyJbAb1RM4oQ5pN7aKeXEURkTkiEVm/Rji2r0agl3Wbw5V23VFn9lCU5/fGyDejRZPtGYsEcFw==", "dev": true, "requires": { "https-proxy-agent": "^2.2.1", @@ -24790,9 +5105,9 @@ "dev": true }, "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", "dev": true }, "buffer-from": { @@ -24845,22 +5160,6 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "camelcase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", - "integrity": "sha1-/AxsNgNj9zd+N5O5oWvM8QcMHKQ=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "map-obj": "^1.0.0" - } - }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -24884,38 +5183,22 @@ "pinkie-promise": "^2.0.0" } }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "error-ex": "^1.2.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "path-exists": { @@ -24927,23 +5210,6 @@ "pinkie-promise": "^2.0.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -24965,6 +5231,12 @@ "read-pkg": "^1.0.0" } }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -24974,15 +5246,6 @@ "ansi-regex": "^2.0.0" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -25061,10 +5324,34 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "camelcase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", + "integrity": "sha512-U4E6A6aFyYnNW+tDt5/yIUKQURKXe3WMFPfX4FxrQFcwZ/R08AUk1xWcUtlr7oq6CV07Ji+aa69V2g7BSpblnQ==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", + "dev": true + } + } + }, + "can-autoplay": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/can-autoplay/-/can-autoplay-3.0.2.tgz", + "integrity": "sha512-Ih6wc7yJB4TylS/mLyAW0Dj5Nh3Gftq/g966TcxgvpNCOzlbqTs85srAq7mwIspo4w8gnLCVVGroyCHfh6l9aA==", + "dev": true + }, "caniuse-lite": { - "version": "1.0.30001390", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", - "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==" + "version": "1.0.30001429", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz", + "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==" }, "caseless": { "version": "0.12.0", @@ -25110,21 +5397,6 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" - }, - "dependencies": { - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } } }, "character-entities": { @@ -25180,9 +5452,9 @@ "dev": true }, "chrome-launcher": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.0.tgz", - "integrity": "sha512-ZQqX5kb9H0+jy1OqLnWampfocrtSZaGl7Ny3F9GRha85o4odbL8x55paUzh51UC7cEmZ5obp3H2Mm70uC2PpRA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.1.tgz", + "integrity": "sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==", "dev": true, "requires": { "@types/node": "*", @@ -25217,6 +5489,12 @@ "static-extend": "^0.1.1" }, "dependencies": { + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -25295,9 +5573,9 @@ } }, "cli-spinners": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", - "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", "dev": true }, "cli-width": { @@ -25307,13 +5585,13 @@ "dev": true }, "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "requires": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, @@ -25330,9 +5608,9 @@ "dev": true }, "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", "dev": true, "requires": { "mimic-response": "^1.0.0" @@ -25423,9 +5701,9 @@ "dev": true }, "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "commondir": { @@ -25521,11 +5799,41 @@ "ms": "2.0.0" } }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true } } }, @@ -25541,13 +5849,6 @@ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { "safe-buffer": "5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } } }, "content-type": { @@ -25562,17 +5863,14 @@ "dev": true }, "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "requires": { - "safe-buffer": "~5.1.1" - } + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", @@ -25596,30 +5894,22 @@ } }, "core-js": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", - "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==" + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz", + "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==" }, "core-js-compat": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", - "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.0.tgz", + "integrity": "sha512-piOX9Go+Z4f9ZiBFLnZ5VrOpBl0h7IGCkiFUN11QTe6LjAvOT3ifL/5TdoizMh99hcGy5SoLyWbapIY/PIb/3A==", "requires": { - "browserslist": "^4.19.1", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } + "browserslist": "^4.21.4" } }, "core-js-pure": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", - "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==" + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", + "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==" }, "core-util-is": { "version": "1.0.3", @@ -25651,14 +5941,10 @@ } }, "crc-32": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.1.tgz", - "integrity": "sha512-Dn/xm/1vFFgs3nfrpEVScHoIslO9NZRITWGz/1E/St6u4xw99vfZzVkW0OSnzx2h9egej9xwMCEut6sqwokM/w==", - "dev": true, - "requires": { - "exit-on-epipe": "~1.0.1", - "printj": "~1.3.1" - } + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true }, "crc32-stream": { "version": "4.0.2", @@ -25744,15 +6030,6 @@ "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", "dev": true }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -25779,9 +6056,9 @@ } }, "date-format": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.4.tgz", - "integrity": "sha512-/jyf4rhB17ge328HJuJjAcmRtCsGd+NDeAtahRBTaK6vSPR6MO5HlrAit3Nn7dVjaa6sowW0WXt8yQtLyZQFRg==", + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true }, "dateformat": { @@ -25798,9 +6075,9 @@ "optional": true }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { "ms": "2.1.2" } @@ -25828,9 +6105,9 @@ } }, "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, "decode-named-character-reference": { @@ -25843,9 +6120,9 @@ } }, "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true }, "decompress-response": { @@ -25925,9 +6202,9 @@ "dev": true }, "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "requires": { "clone": "^1.0.2" @@ -25936,7 +6213,7 @@ "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true } } @@ -25948,11 +6225,13 @@ "dev": true }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "define-property": { @@ -25972,9 +6251,9 @@ "dev": true }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "dequal": { "version": "2.0.3", @@ -25983,9 +6262,9 @@ "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-file": { "version": "1.0.0", @@ -25993,6 +6272,15 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -26006,44 +6294,182 @@ "dev": true }, "devtools": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.16.tgz", - "integrity": "sha512-M0kzkuSgfEhpqIis3gdtWsNjn/HQ+vRAmEzDnbYx/7FfjFxhSv1d+rOOT20pvd60soItMYpsOova1igACEGkGQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.25.4.tgz", + "integrity": "sha512-R6/S/dCqxoX4Y6PxIGM9JFAuSRZzUeV5r+CoE/frhmno6mTe7dEEgwkJlfit3LkKRoul8n4DsL2A3QtWOvq5IA==", "dev": true, "requires": { - "@types/node": "^17.0.4", + "@types/node": "^18.0.0", "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", + "@wdio/config": "7.25.4", + "@wdio/logger": "7.19.0", + "@wdio/protocols": "7.22.0", + "@wdio/types": "7.25.4", + "@wdio/utils": "7.25.4", "chrome-launcher": "^0.15.0", "edge-paths": "^2.1.0", "puppeteer-core": "^13.1.3", "query-selector-shadow-dom": "^1.0.0", "ua-parser-js": "^1.0.1", - "uuid": "^8.0.0" + "uuid": "^9.0.0" }, "dependencies": { + "@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "dev": true + }, + "@wdio/config": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.25.4.tgz", + "integrity": "sha512-vb0emDtD9FbFh/yqW6oNdo2iuhQp8XKj6GX9fyy9v4wZgg3B0HPMVJxhIfcoHz7LMBWlHSo9YdvhFI5EQHRLBA==", + "dev": true, + "requires": { + "@wdio/logger": "7.19.0", + "@wdio/types": "7.25.4", + "@wdio/utils": "7.25.4", + "deepmerge": "^4.0.0", + "glob": "^8.0.3" + } + }, + "@wdio/logger": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.19.0.tgz", + "integrity": "sha512-xR7SN/kGei1QJD1aagzxs3KMuzNxdT/7LYYx+lt6BII49+fqL/SO+5X0FDCZD0Ds93AuQvvz9eGyzrBI2FFXmQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/protocols": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.22.0.tgz", + "integrity": "sha512-8EXRR+Ymdwousm/VGtW3H1hwxZ/1g1H99A1lF0U4GuJ5cFWHCd0IVE5H31Z52i8ZruouW8jueMkGZPSo2IIUSQ==", + "dev": true + }, + "@wdio/types": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.25.4.tgz", + "integrity": "sha512-muvNmq48QZCvocctnbe0URq2FjJjUPIG4iLoeMmyF0AQgdbjaUkMkw3BHYNHVTbSOU9WMsr2z8alhj/I2H6NRQ==", + "dev": true, + "requires": { + "@types/node": "^18.0.0", + "got": "^11.8.1" + } + }, + "@wdio/utils": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.25.4.tgz", + "integrity": "sha512-8iwQDk+foUqSzKZKfhLxjlCKOkfRJPNHaezQoevNgnrTq/t0ek+ldZCATezb9B8jprAuP4mgS9xi22akc6RkzA==", + "dev": true, + "requires": { + "@wdio/logger": "7.19.0", + "@wdio/types": "7.25.4", + "p-iteration": "^1.1.8" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "ua-parser-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.2.tgz", - "integrity": "sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg==", + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", "dev": true }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "dev": true } } }, "devtools-protocol": { - "version": "0.0.973690", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.973690.tgz", - "integrity": "sha512-myh3hSFp0YWa2GED11PmbLhV4dv9RdO7YUz27XJrbQLnP5bMbZL6dfOOILTHO57yH0kX5GfuOZBsg/4NamfPvQ==", + "version": "0.0.1061995", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1061995.tgz", + "integrity": "sha512-pKZZWTjWa/IF4ENCg6GN8bu/AxSZgdhjSa26uc23wz38Blt2Tnm9icOPcSG3Cht55rMq35in1w3rWVPcZ60ArA==", "dev": true }, "di": { @@ -26053,15 +6479,15 @@ "dev": true }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true }, "diff-sequences": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", - "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", + "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", "dev": true }, "dlv": { @@ -26088,9 +6514,9 @@ } }, "documentation": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", - "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.1.tgz", + "integrity": "sha512-Y/brACCE3sNnDJPFiWlhXrqGY+NelLYVZShLGse5bT1KdohP4JkPf5T2KNq1YWhIEbDYl/1tebRLC0WYbPQxVw==", "dev": true, "requires": { "@babel/core": "^7.18.10", @@ -26103,7 +6529,7 @@ "chokidar": "^3.5.3", "diff": "^5.1.0", "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^12.0.0", + "git-url-parse": "^13.1.0", "github-slugger": "1.4.0", "glob": "^8.0.3", "globals-docs": "^2.4.1", @@ -26150,15 +6576,9 @@ } }, "chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "dev": true - }, - "diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz", + "integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==", "dev": true }, "glob": { @@ -26174,12 +6594,6 @@ "once": "^1.3.0" } }, - "ini": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", - "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", - "dev": true - }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -26198,19 +6612,13 @@ "brace-expansion": "^2.0.1" } }, - "strip-json-comments": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", - "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", - "dev": true - }, "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", + "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", "dev": true, "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", @@ -26233,6 +6641,12 @@ "void-elements": "^2.0.0" } }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, "dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", @@ -26245,12 +6659,38 @@ "dev": true }, "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", "dev": true, "requires": { - "readable-stream": "^2.0.2" + "readable-stream": "~1.1.9" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true + } } }, "duplexify": { @@ -26350,14 +6790,14 @@ } }, "electron-to-chromium": { - "version": "1.4.243", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.243.tgz", - "integrity": "sha512-BgLD2gBX43OSXwlT01oYRRD5NIB4n3okTRxkzEAC6G0SZG4TTlyrWMjbOo0fajCwqwpRtMHXQNMjtRN6qpNtfw==" + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" }, "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "emojis-list": { @@ -26381,9 +6821,9 @@ } }, "engine.io": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", - "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz", + "integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -26396,21 +6836,26 @@ "debug": "~4.3.1", "engine.io-parser": "~5.0.3", "ws": "~8.2.3" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + } } }, "engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "dev": true, - "requires": { - "@socket.io/base64-arraybuffer": "~1.0.2" - } + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", + "dev": true }, "enhanced-resolve": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", - "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -26460,31 +6905,35 @@ } }, "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", + "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" } }, "es-get-iterator": { @@ -26509,6 +6958,15 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -26521,9 +6979,9 @@ } }, "es5-ext": { - "version": "0.10.57", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.57.tgz", - "integrity": "sha512-L7cCNoPwTkAp7IBHxrKLsh7NKiVFkcdxlP9vbVw9QUvb7gF0Mz9bEBN0WY9xqdTjGF907EMT/iG013vnbqwu1Q==", + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", "dev": true, "requires": { "es6-iterator": "^2.0.3", @@ -26532,9 +6990,9 @@ } }, "es5-shim": { - "version": "4.6.5", - "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.5.tgz", - "integrity": "sha512-vfQ4UAai8szn0sAubCy97xnZ4sJVDD1gt/Grn736hg8D7540wemIb1YPrYZSTqlM2H69EQX1or4HU/tSwRTI3w==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", + "integrity": "sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==", "dev": true }, "es6-iterator": { @@ -26733,18 +7191,6 @@ "@babel/highlight": "^7.10.4" } }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -26786,18 +7232,24 @@ "dev": true }, "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -26854,13 +7306,12 @@ } }, "eslint-module-utils": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", - "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", "dev": true, "requires": { - "debug": "^3.2.7", - "find-up": "^2.1.0" + "debug": "^3.2.7" }, "dependencies": { "debug": { @@ -26885,9 +7336,9 @@ } }, "eslint-plugin-import": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz", - "integrity": "sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "dev": true, "requires": { "array-includes": "^3.1.4", @@ -26895,14 +7346,14 @@ "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.2", + "eslint-module-utils": "^2.7.3", "has": "^1.0.3", - "is-core-module": "^2.8.0", + "is-core-module": "^2.8.1", "is-glob": "^4.0.3", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "object.values": "^1.1.5", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.12.0" + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" }, "dependencies": { "debug": { @@ -26926,7 +7377,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true } } @@ -26954,7 +7405,8 @@ } }, "eslint-plugin-prebid": { - "version": "file:plugins/eslint" + "version": "file:plugins/eslint", + "dev": true }, "eslint-plugin-promise": { "version": "5.2.0", @@ -27194,12 +7646,6 @@ } } }, - "exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "dev": true - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -27323,59 +7769,88 @@ } }, "expect": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", - "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", + "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", "dev": true, "requires": { - "@jest/types": "^27.5.1", - "jest-get-type": "^27.5.1", - "jest-matcher-utils": "^27.5.1", - "jest-message-util": "^27.5.1" + "@jest/types": "^26.6.2", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-regex-util": "^26.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } } }, "expect-webdriverio": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-3.1.4.tgz", - "integrity": "sha512-65FTS3bmxcIp0cq6fLb/72TrCQXBCpwPLC7SwMWdpPlLq461mXcK1BPKJJjnIC587aXSKD+3E4hvnlCtwDmXfg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-2.0.2.tgz", + "integrity": "sha512-dst0tqP1aZ2p7TPmbatqoIQ+7hRTw+IeKNi830XxKhu2DNNe5vQ85i9ttf9rpXgbnUf91HxKcocn4G7A5bQxDA==", "dev": true, "requires": { - "expect": "^27.0.2", - "jest-matcher-utils": "^27.0.2" + "expect": "^26.6.2", + "jest-matcher-utils": "^26.6.2" } }, "express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.1", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -27392,28 +7867,23 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "ext": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", - "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", "dev": true, "requires": { - "type": "^2.5.0" + "type": "^2.7.2" }, "dependencies": { "type": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", "dev": true } } @@ -27425,13 +7895,20 @@ "dev": true }, "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha512-L7AGmkO6jhDkEBBGWlLtftA80Xq8DipnrRPr0pyi7GQLXkaq9JYA4xF4z6qnadIC6euiTDKco0cGSU9muw+WTw==", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "kind-of": "^1.1.0" + }, + "dependencies": { + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha512-aUH6ElPnMGon2/YkxRIigV32MOpTVcoXQ1Oo8aYn40s+sJ3j+0gFZsT8HKDcxNy7Fi9zuquWtGaGAahOdv5p/g==", + "dev": true + } } }, "external-editor": { @@ -27443,17 +7920,6 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" - }, - "dependencies": { - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - } } }, "extglob": { @@ -27582,9 +8048,9 @@ } }, "fibers": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.1.tgz", - "integrity": "sha512-VMC7Frt87Oo0AOJ6EcPFbi+tZmkQ4tD85aatwyWL6I9cYMJmm2e+pXUJsfGZ36U7MffXtjou2XIiWJMtHriErw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.3.tgz", + "integrity": "sha512-/qYTSoZydQkM21qZpGLDLuCq8c+B8KhuCQ1kLPvnRNhxhVbvrpmH9l2+Lblf5neDuEsY4bfT7LeO553TXQDvJw==", "dev": true, "requires": { "detect-libc": "^1.0.3" @@ -27654,16 +8120,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -27678,7 +8144,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -27694,12 +8160,13 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "findup-sync": { @@ -27712,6 +8179,151 @@ "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, "fined": { @@ -27761,9 +8373,9 @@ } }, "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, "flush-write-stream": { @@ -27777,11 +8389,20 @@ } }, "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "dev": true }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -27797,12 +8418,6 @@ "for-in": "^1.0.1" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, "foreachasync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", @@ -27864,9 +8479,9 @@ "dev": true }, "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", @@ -27964,12 +8579,12 @@ }, "dependencies": { "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "rimraf": { @@ -27996,12 +8611,30 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -28029,13 +8662,13 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.3" } }, "get-package-type": { @@ -28050,12 +8683,6 @@ "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -28091,22 +8718,22 @@ } }, "git-up": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", - "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", "dev": true, "requires": { "is-ssh": "^1.4.0", - "parse-url": "^7.0.2" + "parse-url": "^8.1.0" } }, "git-url-parse": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", - "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", "dev": true, "requires": { - "git-up": "^6.0.0" + "git-up": "^7.0.0" } }, "github-slugger": { @@ -28116,15 +8743,15 @@ "dev": true }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -28219,6 +8846,12 @@ } } }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -28325,12 +8958,93 @@ "binary-extensions": "^1.0.0" } }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -28354,6 +9068,16 @@ } } }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -28378,6 +9102,12 @@ "which": "^1.2.14" }, "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -28401,13 +9131,13 @@ "dev": true }, "globule": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz", - "integrity": "sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", "dev": true, "requires": { "glob": "~7.1.1", - "lodash": "~4.17.10", + "lodash": "^4.17.21", "minimatch": "~3.0.2" }, "dependencies": { @@ -28465,9 +9195,9 @@ } }, "graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, "grapheme-splitter": { @@ -28492,400 +9222,38 @@ "gulp-cli": "^2.2.0", "undertaker": "^1.2.1", "vinyl-fs": "^3.0.0" - } - }, - "gulp-clean": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", - "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", - "dev": true, - "requires": { - "gulp-util": "^2.2.14", - "rimraf": "^2.2.8", - "through2": "^0.4.2" - }, - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", - "dev": true - }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", - "dev": true - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", - "dev": true, - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - } - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "gulp-util": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", - "dev": true, - "requires": { - "chalk": "^0.5.0", - "dateformat": "^1.0.7-1.2.3", - "lodash._reinterpolate": "^2.4.1", - "lodash.template": "^2.4.1", - "minimist": "^0.2.0", - "multipipe": "^0.1.0", - "through2": "^0.5.0", - "vinyl": "^0.2.1" - }, - "dependencies": { - "through2": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", - "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", - "dev": true, - "requires": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" - } - }, - "xtend": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", - "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", - "dev": true - } - } - }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", - "dev": true, - "requires": { - "ansi-regex": "^0.2.0" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "lodash._reinterpolate": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", - "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", - "dev": true - }, - "lodash.defaults": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", - "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, - "lodash.template": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", - "dev": true, - "requires": { - "lodash._escapestringchar": "~2.4.1", - "lodash._reinterpolate": "~2.4.1", - "lodash.defaults": "~2.4.1", - "lodash.escape": "~2.4.1", - "lodash.keys": "~2.4.1", - "lodash.templatesettings": "~2.4.1", - "lodash.values": "~2.4.1" - } - }, - "lodash.templatesettings": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", - "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", - "dev": true, - "requires": { - "lodash._reinterpolate": "~2.4.1", - "lodash.escape": "~2.4.1" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - } - } - }, - "minimist": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", - "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", - "dev": true - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", - "dev": true, - "requires": { - "ansi-regex": "^0.2.1" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true - }, - "through2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", - "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", - "dev": true, - "requires": { - "readable-stream": "~1.0.17", - "xtend": "~2.1.1" - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "vinyl": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", - "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + } + }, + "gulp-clean": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.4.0.tgz", + "integrity": "sha512-DARK8rNMo4lHOFLGTiHEJdf19GuoBDHqGUaypz+fOhrvOs3iFO7ntdYtdpNxv+AzSJBx/JfypF0yEj9ks1IStQ==", + "dev": true, + "requires": { + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2", + "rimraf": "^2.6.2", + "through2": "^2.0.3", + "vinyl": "^2.1.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "clone-stats": "~0.0.1" + "glob": "^7.1.3" } }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "object-keys": "~0.4.0" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } } } @@ -28948,6 +9316,12 @@ "wrap-ansi": "^2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -28964,6 +9338,12 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -28973,26 +9353,16 @@ "number-is-nan": "^1.0.0" } }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "error-ex": "^1.2.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "path-exists": { @@ -29004,23 +9374,6 @@ "pinkie-promise": "^2.0.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -29042,10 +9395,10 @@ "read-pkg": "^1.0.0" } }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "string-width": { @@ -29068,21 +9421,6 @@ "ansi-regex": "^2.0.0" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -29187,6 +9525,18 @@ "ms": "2.0.0" } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "dev": true + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -29217,6 +9567,15 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -29263,12 +9622,33 @@ "plugin-error": "^1.0.1" }, "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, "ansi-regex": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -29362,18 +9742,6 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -29411,6 +9779,16 @@ "eslint-visitor-keys": "^1.1.0" } }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, "file-entry-cache": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", @@ -29446,6 +9824,12 @@ "type-fest": "^0.8.1" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "inquirer": { "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", @@ -29533,6 +9917,18 @@ "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -29554,15 +9950,6 @@ "glob": "^7.1.3" } }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -29755,9 +10142,9 @@ }, "dependencies": { "@types/node": { - "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", + "version": "14.18.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.33.tgz", + "integrity": "sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==", "dev": true } } @@ -29776,6 +10163,15 @@ "tslib": "^1.10.0" }, "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -29785,6 +10181,18 @@ "color-convert": "^2.0.1" } }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -29810,6 +10218,34 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -29884,6 +10320,51 @@ "terser": "^5.9.0", "through2": "^4.0.2", "vinyl-sourcemaps-apply": "^0.2.1" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + } } }, "gulp-util": { @@ -29949,26 +10430,6 @@ "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", "dev": true }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "lodash.template": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", @@ -30093,20 +10554,6 @@ "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - } } }, "has": { @@ -30135,16 +10582,15 @@ } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true }, "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "has-gulplog": { "version": "0.1.0", @@ -30155,6 +10601,15 @@ "sparkles": "^1.0.0" } }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -30196,6 +10651,26 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -30251,9 +10726,9 @@ "dev": true }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "highlight.js": { @@ -30282,10 +10757,13 @@ } }, "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "html-escaper": { "version": "2.0.2", @@ -30300,27 +10778,27 @@ "dev": true }, "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "http-parser-js": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", - "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==", + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", "dev": true }, "http-proxy": { @@ -30356,12 +10834,12 @@ } }, "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "requires": { - "agent-base": "5", + "agent-base": "6", "debug": "4" } }, @@ -30403,10 +10881,22 @@ } } }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "dev": true + }, + "individual": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", + "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==", "dev": true }, "inflight": { @@ -30425,15 +10915,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", "dev": true }, "inquirer": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.5.tgz", - "integrity": "sha512-G6/9xUqmt/r+UvufSyrPpt84NYwhKZ9jLsgMbQzlx804XErNupor8WQdBnBRrXmBfTPpuwf1sV+ss2ovjgdXIg==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -30446,10 +10936,11 @@ "mute-stream": "0.0.8", "ora": "^5.4.1", "run-async": "^2.4.0", - "rxjs": "^7.2.0", + "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", - "through": "^2.3.6" + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" }, "dependencies": { "ansi-styles": { @@ -30486,6 +10977,21 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -30494,6 +11000,12 @@ "requires": { "has-flag": "^4.0.0" } + }, + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "dev": true } } }, @@ -30612,15 +11124,15 @@ "dev": true }, "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true }, "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "requires": { "has": "^1.0.3" } @@ -30714,6 +11226,12 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true + }, "is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -30767,35 +11285,15 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, "requires": { "has-tostringtag": "^1.0.0" @@ -30851,10 +11349,13 @@ "dev": true }, "is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } }, "is-ssh": { "version": "1.4.0", @@ -30890,15 +11391,15 @@ } }, "is-typed-array": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", - "integrity": "sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.9.tgz", + "integrity": "sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A==", "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", + "es-abstract": "^1.20.0", + "for-each": "^0.3.3", "has-tostringtag": "^1.0.0" } }, @@ -30982,9 +11483,9 @@ "dev": true }, "isbinaryfile": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", - "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true }, "isexe": { @@ -31047,12 +11548,12 @@ "dev": true }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "resolve": { @@ -31087,6 +11588,19 @@ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, "istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -31098,6 +11612,12 @@ "supports-color": "^7.1.0" }, "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -31129,9 +11649,9 @@ } }, "istanbul-reports": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", - "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -31200,6 +11720,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -31212,15 +11738,15 @@ } }, "jest-diff": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", - "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", + "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "diff-sequences": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" }, "dependencies": { "ansi-styles": { @@ -31257,6 +11783,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -31269,21 +11801,21 @@ } }, "jest-get-type": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", - "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", "dev": true }, "jest-matcher-utils": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", - "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", + "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^27.5.1", - "jest-get-type": "^27.5.1", - "pretty-format": "^27.5.1" + "jest-diff": "^26.6.2", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.6.2" }, "dependencies": { "ansi-styles": { @@ -31320,6 +11852,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -31332,20 +11870,20 @@ } }, "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.2" }, "dependencies": { "ansi-styles": { @@ -31382,15 +11920,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true }, "supports-color": { "version": "7.2.0", @@ -31403,6 +11943,12 @@ } } }, + "jest-regex-util": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "dev": true + }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -31412,6 +11958,23 @@ "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "js-tokens": { @@ -31454,12 +12017,6 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -31535,9 +12092,9 @@ "dev": true }, "karma": { - "version": "6.3.17", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz", - "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", + "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", "dev": true, "requires": { "@colors/colors": "1.5.0", @@ -31559,20 +12116,31 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^4.2.0", + "socket.io": "^4.4.1", "source-map": "^0.6.1", "tmp": "^0.2.1", "ua-parser-js": "^0.7.30", "yargs": "^16.1.1" }, "dependencies": { + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } }, "source-map": { @@ -31581,6 +12149,15 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -31628,9 +12205,9 @@ "dev": true }, "karma-chrome-launcher": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", - "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", + "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", "dev": true, "requires": { "which": "^1.2.1" @@ -31659,21 +12236,6 @@ "istanbul-lib-source-maps": "^4.0.1", "istanbul-reports": "^3.0.5", "minimatch": "^3.0.4" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - } } }, "karma-coverage-istanbul-reporter": { @@ -31867,10 +12429,16 @@ "webpack-merge": "^4.1.5" } }, + "keycode": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz", + "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==", + "dev": true + }, "keyv": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.1.1.tgz", - "integrity": "sha512-tGv1yP6snQVDSM4X6yxrv2zzq/EvpW+oYiUz6aueW1u9CtS8RzUQYxxmFwgZlO2jSgCxQbchhxaqXXp2hnKGpQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", + "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -32024,9 +12592,9 @@ "dev": true }, "live-connect-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.4.0.tgz", - "integrity": "sha512-MSBLKfnXoxH+pqwji/Mf8yZu3VZMq4tnNfwMw7NTWN5a+TBM6f0RWgwui1YMA3nHmMhX/nzxxsso0SkyKcF0fA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-4.0.0.tgz", + "integrity": "sha512-uycBgFBdEwSq95NImrsOSkTlszUMTGf8luK9GZDWw4D+DL5yFNnCPcrjxUk15U9n9aPmaM1SKmWH5qUXFr8aIA==", "requires": { "tiny-hashes": "1.0.1" } @@ -32037,20 +12605,69 @@ "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", "dev": true }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "lodash": { @@ -32077,51 +12694,18 @@ "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", "dev": true }, - "lodash._escapehtmlchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", - "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", - "dev": true, - "requires": { - "lodash._htmlescapes": "~2.4.1" - } - }, - "lodash._escapestringchar": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", - "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", - "dev": true - }, "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", "dev": true }, - "lodash._htmlescapes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", - "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", - "dev": true - }, "lodash._isiterateecall": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", "dev": true }, - "lodash._isnative": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", - "dev": true - }, - "lodash._objecttypes": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", - "dev": true - }, "lodash._reescape": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", @@ -32140,31 +12724,12 @@ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._reunescapedhtml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", - "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", - "dev": true, - "requires": { - "lodash._htmlescapes": "~2.4.1", - "lodash.keys": "~2.4.1" - } - }, "lodash._root": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, - "lodash._shimkeys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", - "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1" - } - }, "lodash.clone": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", @@ -32195,14 +12760,12 @@ "dev": true }, "lodash.escape": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", - "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha512-n1PZMXgaaDWZDSvuNZ/8XOcYO2hOKDqZel5adtR30VKQAtoWs/5AOeFA0vPV8moiPzlqe7F4cP2tzpFewQyelQ==", "dev": true, "requires": { - "lodash._escapehtmlchar": "~2.4.1", - "lodash._reunescapedhtml": "~2.4.1", - "lodash.keys": "~2.4.1" + "lodash._root": "^3.0.0" } }, "lodash.flatten": { @@ -32248,25 +12811,14 @@ "dev": true }, "lodash.keys": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", - "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ==", "dev": true, "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" - }, - "dependencies": { - "lodash.isobject": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", - "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", - "dev": true, - "requires": { - "lodash._objecttypes": "~2.4.1" - } - } + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" } }, "lodash.merge": { @@ -32324,15 +12876,6 @@ "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", "dev": true }, - "lodash.values": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", - "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", - "dev": true, - "requires": { - "lodash.keys": "~2.4.1" - } - }, "lodash.zip": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", @@ -32355,16 +12898,16 @@ } }, "log4js": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.2.tgz", - "integrity": "sha512-k80cggS2sZQLBwllpT1p06GtfvzMmSdUCkW96f0Hj83rKGJDAu2vZjt9B9ag2vx8Zz1IXzxoLgqvRJCdMKybGg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.7.0.tgz", + "integrity": "sha512-KA0W9ffgNBLDj6fZCq/lRbgR6ABAodRIDHrZnS48vOtfKa4PzWImb0Md1lmGCdO3n3sbCm/n1/WmrNlZ8kCI3Q==", "dev": true, "requires": { - "date-format": "^4.0.4", - "debug": "^4.3.3", - "flatted": "^3.2.5", + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", "rfdc": "^1.3.0", - "streamroller": "^3.0.4" + "streamroller": "^3.1.3" } }, "loglevel": { @@ -32400,16 +12943,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, "loupe": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", @@ -32443,6 +12976,17 @@ "es5-ext": "~0.10.2" } }, + "m3u8-parser": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.1.tgz", + "integrity": "sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" + } + }, "magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", @@ -32485,6 +13029,12 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true + }, "map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -32507,9 +13057,9 @@ "dev": true }, "marky": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.4.tgz", - "integrity": "sha512-zd2/GiSn6U3/jeFVZ0J9CA1LzQ8RfIVvXkb/U0swFHF/zT+dVohTAWjmo2DcIuofmIIIROlwTbd+shSeXmxr0w==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", "dev": true }, "matchdep": { @@ -32524,6 +13074,86 @@ "stack-trace": "0.0.10" }, "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, "findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", @@ -32536,6 +13166,12 @@ "resolve-dir": "^1.0.1" } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", @@ -32544,6 +13180,63 @@ "requires": { "is-extglob": "^2.1.0" } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, @@ -32654,11 +13347,12 @@ } }, "mdast-util-gfm-table": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.4.tgz", - "integrity": "sha512-aEuoPwZyP4iIMkf2cLWXxx3EQ6Bmh2yKy9MVCg4i6Sd3cX80dcLEfXO/V4ul3pGH9czBK4kp+FAl+ZHmSUt9/w==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.6.tgz", + "integrity": "sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==", "dev": true, "requires": { + "@types/mdast": "^3.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^1.0.0", "mdast-util-to-markdown": "^1.3.0" @@ -32684,17 +13378,15 @@ } }, "mdast-util-to-hast": { - "version": "12.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.1.tgz", - "integrity": "sha512-dyindR2P7qOqXO1hQirZeGtVbiX7xlNQbw7gGaAwN4A1dh4+X8xU/JyYmRoyB8Fu1uPXzp7mlL5QwW7k+knvgA==", + "version": "12.2.4", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.4.tgz", + "integrity": "sha512-a21xoxSef1l8VhHxS1Dnyioz6grrJkoaCUgGzMD/7dWHvboYX3VW53esRUfB5tgTyz4Yos1n25SPcj35dJqmAg==", "dev": true, "requires": { "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", - "@types/mdurl": "^1.0.0", "mdast-util-definitions": "^5.0.0", - "mdurl": "^1.0.0", - "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-sanitize-uri": "^1.1.0", "trim-lines": "^3.0.0", "unist-builder": "^3.0.0", "unist-util-generated": "^2.0.0", @@ -32776,12 +13468,6 @@ } } }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -32830,9 +13516,9 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromark": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", - "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.1.0.tgz", + "integrity": "sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==", "dev": true, "requires": { "@types/debug": "^4.0.0", @@ -33122,9 +13808,9 @@ } }, "micromark-util-sanitize-uri": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", - "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.1.0.tgz", + "integrity": "sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==", "dev": true, "requires": { "micromark-util-character": "^1.0.0", @@ -33157,100 +13843,13 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime": { @@ -33260,16 +13859,16 @@ "dev": true }, "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -33284,6 +13883,15 @@ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dev": true, + "requires": { + "dom-walk": "^0.1.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -33294,9 +13902,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, "mixin-deep": { @@ -33322,43 +13930,128 @@ "dev": true }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz", + "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==", "dev": true, "requires": { + "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "ms": "2.0.0" + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -33367,52 +14060,132 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "minimist": "0.0.8" + "yocto-queue": "^0.1.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true } } }, @@ -33438,20 +14211,35 @@ "ms": "2.0.0" } }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } } } }, + "mpd-parser": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.1.tgz", + "integrity": "sha512-BxlSXWbKE1n7eyEPBnTEkrzhS3PdmkkKdM1pgKbPnPOH0WFZIc0sPOWi7m0Uo3Wd2a4Or8Qf4ZbS7+ASqQ49fw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "@xmldom/xmldom": "^0.7.2", + "global": "^4.4.0" + } + }, "mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -33459,9 +14247,9 @@ "dev": true }, "mrmime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", - "integrity": "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", "dev": true }, "ms": { @@ -33476,41 +14264,6 @@ "dev": true, "requires": { "duplexer2": "0.0.2" - }, - "dependencies": { - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } } }, "mute-stdout": { @@ -33525,19 +14278,28 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "mux.js": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz", + "integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==", + "dev": true, + "requires": { + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" + } + }, "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", "dev": true, "optional": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "optional": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true }, "nanomatch": { "version": "1.2.13", @@ -33558,6 +14320,22 @@ "to-regex": "^3.0.1" }, "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -33674,22 +14452,25 @@ } }, "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -33824,10 +14605,9 @@ } }, "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", - "dev": true + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "object-is": { "version": "1.1.5", @@ -33842,7 +14622,8 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, "object-visit": { "version": "1.0.1", @@ -33854,13 +14635,14 @@ } }, "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", "object-keys": "^1.1.1" } }, @@ -33917,9 +14699,9 @@ } }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -34036,6 +14818,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -34106,27 +14894,27 @@ "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.2.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "parent-module": { @@ -34180,24 +14968,21 @@ "dev": true }, "parse-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", - "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dev": true, "requires": { "protocols": "^2.0.0" } }, "parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", - "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, "requires": { - "is-ssh": "^1.4.0", - "normalize-url": "^6.1.0", - "parse-path": "^5.0.0", - "protocols": "^2.0.1" + "parse-path": "^7.0.0" } }, "parseurl": { @@ -34218,9 +15003,9 @@ "dev": true }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-is-absolute": { @@ -34260,6 +15045,25 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -34319,6 +15123,15 @@ "pinkie": "^2.0.0" } }, + "pkcs7": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", + "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.5.5" + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -34326,80 +15139,19 @@ "dev": true, "requires": { "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } } }, "plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==", "dev": true, "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "dependencies": { - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - } + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" } }, "posix-character-classes": { @@ -34409,15 +15161,24 @@ "dev": true }, "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.18", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", + "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==", "dev": true, "optional": true, "requires": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "optional": true + } } }, "prelude-ls": { @@ -34427,20 +15188,39 @@ "dev": true }, "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", "react-is": "^17.0.1" }, "dependencies": { "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } @@ -34460,18 +15240,18 @@ "parse-ms": "^2.1.0" } }, - "printj": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.3.1.tgz", - "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", - "dev": true - }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -34533,9 +15313,9 @@ "dev": true }, "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, "pump": { @@ -34590,16 +15370,16 @@ "dev": true }, "puppeteer-core": { - "version": "13.5.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.5.1.tgz", - "integrity": "sha512-dobVqWjV34ilyfQHR3BBnCYaekBYTi5MgegEYBRYd3s3uFy8jUpZEEWbaFjG9ETm+LGzR5Lmr0aF6LLuHtiuCg==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz", + "integrity": "sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q==", "dev": true, "requires": { "cross-fetch": "3.1.5", - "debug": "4.3.3", - "devtools-protocol": "0.0.969999", + "debug": "4.3.4", + "devtools-protocol": "0.0.981744", "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", "pkg-dir": "4.2.0", "progress": "2.0.3", "proxy-from-env": "1.1.0", @@ -34609,31 +15389,12 @@ "ws": "8.5.0" }, "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, "devtools-protocol": { - "version": "0.0.969999", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.969999.tgz", - "integrity": "sha512-6GfzuDWU0OFAuOvBokXpXPLxjOJ5DZ157Ue3sGQQM3LgAamb8m0R0ruSfN0DDu+XG5XJgT50i6zZ/0o8RglreQ==", + "version": "0.0.981744", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", + "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", "dev": true }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, "ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", @@ -34655,9 +15416,12 @@ "dev": true }, "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } }, "query-selector-shadow-dom": { "version": "1.0.0", @@ -34698,12 +15462,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -34726,36 +15490,6 @@ "type-fest": "^2.0.0" }, "dependencies": { - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, "type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -34852,16 +15586,42 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true } } }, "readdir-glob": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.2.tgz", + "integrity": "sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==", "dev": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "^5.1.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "readdirp": { @@ -34883,23 +15643,12 @@ } }, "recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "dev": true, "requires": { - "minimatch": "3.0.4" - }, - "dependencies": { - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } + "minimatch": "^3.0.5" } }, "regenerate": { @@ -34908,23 +15657,22 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", - "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", "requires": { "regenerate": "^1.4.2" } }, "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", + "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==" }, "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", "requires": { "@babel/runtime": "^7.8.4" } @@ -34937,16 +15685,29 @@ "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + } } }, "regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpp": { @@ -34956,27 +15717,27 @@ "dev": true }, "regexpu-core": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", - "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", + "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", "requires": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.0.1", - "regjsgen": "^0.6.0", - "regjsparser": "^0.8.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.0.0" } }, "regjsgen": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==" }, "regjsparser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", - "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", "requires": { "jsesc": "~0.5.0" }, @@ -34984,7 +15745,7 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" } } }, @@ -35213,6 +15974,12 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -35267,9 +16034,9 @@ "dev": true }, "responselike": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", - "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", "dev": true, "requires": { "lowercase-keys": "^2.0.0" @@ -35335,21 +16102,22 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, - "rxjs": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", - "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "rust-result": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", + "integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==", "dev": true, "requires": { - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - } + "individual": "^2.0.0" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" } }, "sade": { @@ -35362,9 +16130,9 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-json-parse": { "version": "1.0.1", @@ -35381,6 +16149,17 @@ "ret": "~0.1.10" } }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -35414,12 +16193,6 @@ "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true } } }, @@ -35438,23 +16211,23 @@ } }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -35468,7 +16241,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -35534,6 +16307,12 @@ "ms": "2.0.0" } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -35563,18 +16342,24 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true } } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "set-blocking": { @@ -35651,7 +16436,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -35684,21 +16468,6 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -35714,9 +16483,9 @@ } }, "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", "dev": true }, "slice-ansi": { @@ -35932,33 +16701,32 @@ } }, "socket.io": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", - "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz", + "integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==", "dev": true, "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.0" } }, "socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", "dev": true }, "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", "dev": true, "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } }, @@ -35992,21 +16760,12 @@ } }, "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "source-map": "^0.5.6" } }, "source-map-url": { @@ -36061,9 +16820,9 @@ } }, "spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", + "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", "dev": true }, "split": { @@ -36082,13 +16841,41 @@ "dev": true, "requires": { "extend-shallow": "^3.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + } } }, "split2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", - "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", - "dev": true + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } }, "sprintf-js": { "version": "1.0.3", @@ -36215,9 +17002,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "stream-buffers": { "version": "3.0.2", @@ -36247,23 +17034,42 @@ "dev": true }, "streamroller": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.4.tgz", - "integrity": "sha512-GI9NzeD+D88UFuIlJkKNDH/IsuR+qIN7Qh8EsmhoRZr9bQoehTraRgwtLUkZbpcAw+hLPfHOypmppz8YyGK68w==", - "dev": true, - "requires": { - "date-format": "^4.0.4", - "debug": "^4.3.3", - "fs-extra": "^10.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.3.tgz", + "integrity": "sha512-CphIJyFx2SALGHeINanjFRKQ4l7x2c+rXYJ4BMq0gd+ZK0gi4VT8b+eHe2wi58x4UayBAKx4xtHpXT/ea1cz8w==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } } }, "string-template": { @@ -36281,34 +17087,45 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - } } }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "stringify-entities": { @@ -36348,6 +17165,12 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-json-comments": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", + "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", + "dev": true + }, "suffix": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", @@ -36355,12 +17178,11 @@ "dev": true }, "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { - "has-flag": "^4.0.0" + "has-flag": "^3.0.0" } }, "supports-preserve-symlinks-flag": { @@ -36392,9 +17214,9 @@ }, "dependencies": { "ajv": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", - "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -36500,9 +17322,9 @@ } }, "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", + "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", @@ -36512,30 +17334,40 @@ }, "dependencies": { "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } } } }, "terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.14", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "terser": "^5.14.1" }, "dependencies": { "ajv": { @@ -36550,12 +17382,6 @@ "uri-js": "^4.2.2" } }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -36566,12 +17392,6 @@ "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -36695,12 +17515,12 @@ } }, "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "rimraf": "^3.0.0" + "os-tmpdir": "~1.0.2" } }, "to-absolute-glob": { @@ -36754,6 +17574,18 @@ "extend-shallow": "^3.0.2", "regex-not": "^1.0.2", "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + } } }, "to-regex-range": { @@ -36763,14 +17595,6 @@ "dev": true, "requires": { "is-number": "^7.0.0" - }, - "dependencies": { - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - } } }, "to-through": { @@ -36846,14 +17670,14 @@ "dev": true }, "tsconfig-paths": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.13.0.tgz", - "integrity": "sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "dev": true, "requires": { "@types/json5": "^0.0.29", "json5": "^1.0.1", - "minimist": "^1.2.0", + "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, "dependencies": { @@ -36953,27 +17777,27 @@ } }, "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", + "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", "dev": true }, "uglify-js": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.2.tgz", - "integrity": "sha512-peeoTk3hSwYdoc9nrdiEJk+gx1ALCtTjdYuKSXMTDqq7n1W7dHPqWDdSi+BPL0ni2YMeHD7hKUSdbj3TZauY2A==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "dev": true, "optional": true }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, @@ -37045,9 +17869,9 @@ "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" }, "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" }, "unified": { "version": "10.1.2", @@ -37076,6 +17900,12 @@ "set-value": "^2.0.1" }, "dependencies": { + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -37226,6 +18056,17 @@ "listenercount": "~1.0.1", "readable-stream": "~2.3.6", "setimmediate": "~1.0.4" + }, + "dependencies": { + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + } } }, "upath": { @@ -37235,9 +18076,9 @@ "dev": true }, "update-browserslist-db": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", - "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -37286,6 +18127,12 @@ "requires-port": "^1.0.0" } }, + "url-toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", + "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==", + "dev": true + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -37293,16 +18140,15 @@ "dev": true }, "util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "dev": true, "requires": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", "which-typed-array": "^1.1.2" } }, @@ -37391,9 +18237,9 @@ } }, "vfile": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz", - "integrity": "sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.5.tgz", + "integrity": "sha512-U1ho2ga33eZ8y8pkbQLH54uKqGhFJ6GYIHnnG5AhRpAh3OWjkrRHKa/KogbmQn8We+c0KVV3rTOgR9V/WowbXQ==", "dev": true, "requires": { "@types/unist": "^2.0.0", @@ -37432,6 +18278,12 @@ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -37478,6 +18330,87 @@ "vfile-message": "^3.0.0" } }, + "video.js": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.20.3.tgz", + "integrity": "sha512-JMspxaK74LdfWcv69XWhX4rILywz/eInOVPdKefpQiZJSMD5O8xXYueqACP2Q5yqKstycgmmEKlJzZ+kVmDciw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "2.14.3", + "@videojs/vhs-utils": "^3.0.4", + "@videojs/xhr": "2.6.0", + "aes-decrypter": "3.1.3", + "global": "^4.4.0", + "keycode": "^2.2.0", + "m3u8-parser": "4.7.1", + "mpd-parser": "0.21.1", + "mux.js": "6.0.1", + "safe-json-parse": "4.0.0", + "videojs-font": "3.2.0", + "videojs-vtt.js": "^0.15.4" + }, + "dependencies": { + "safe-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", + "integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==", + "dev": true, + "requires": { + "rust-result": "^1.0.0" + } + } + } + }, + "videojs-contrib-ads": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/videojs-contrib-ads/-/videojs-contrib-ads-6.9.0.tgz", + "integrity": "sha512-nzKz+jhCGMTYffSNVYrmp9p70s05v6jUMOY3Z7DpVk3iFrWK4Zi/BIkokDWrMoHpKjdmCdKzfJVBT+CrUj6Spw==", + "dev": true, + "requires": { + "global": "^4.3.2", + "video.js": "^6 || ^7" + } + }, + "videojs-font": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz", + "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==", + "dev": true + }, + "videojs-ima": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-1.11.0.tgz", + "integrity": "sha512-ZRoWuGyJ75zamwZgpr0i/gZ6q7Evda/Q6R46gpW88WN7u0ORU7apw/lM1MSG4c3YDXW8LDENgzMAvMZUdifWhg==", + "dev": true, + "requires": { + "@hapi/cryptiles": "^5.1.0", + "can-autoplay": "^3.0.0", + "extend": ">=3.0.2", + "lodash": ">=4.17.19", + "lodash.template": ">=4.5.0", + "videojs-contrib-ads": "^6.6.5" + } + }, + "videojs-playlist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.0.0.tgz", + "integrity": "sha512-TM9bytwKqkE05wdWPEKDpkwMGhS/VgMCIsEuNxmX1J1JO9zaTIl4Wm3egf5j1dhIw19oWrqGUV/nK0YNIelCpA==", + "dev": true, + "requires": { + "global": "^4.3.2", + "video.js": "^6 || ^7" + } + }, + "videojs-vtt.js": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz", + "integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==", + "dev": true, + "requires": { + "global": "^4.3.1" + } + }, "vinyl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", @@ -37579,23 +18512,14 @@ "dev": true }, "vue-template-compiler": { - "version": "2.7.10", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz", - "integrity": "sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==", + "version": "2.7.13", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.13.tgz", + "integrity": "sha512-jYM6TClwDS9YqP48gYrtAtaOhRKkbYmbzE+Q51gX5YDr777n7tNI/IZk4QV4l/PjQPNh/FVa/E92sh/RqKMrog==", "dev": true, "optional": true, "requires": { "de-indent": "^1.0.2", "he": "^1.2.0" - }, - "dependencies": { - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "optional": true - } } }, "walk": { @@ -37608,9 +18532,9 @@ } }, "watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -37627,44 +18551,113 @@ } }, "webdriver": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.16.tgz", - "integrity": "sha512-x8UoG9k/P8KDrfSh1pOyNevt9tns3zexoMxp9cKnyA/7HYSErhZYTLGlgxscAXLtQG41cMH/Ba/oBmOx7Hgd8w==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.5.3.tgz", + "integrity": "sha512-cDTn/hYj5x8BYwXxVb/WUwqGxrhCMP2rC8ttIWCfzmiVtmOnJGulC7CyxU3+p9Q5R/gIKTzdJOss16dhb+5CoA==", "dev": true, "requires": { - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", + "@wdio/config": "7.5.3", + "@wdio/logger": "7.5.3", + "@wdio/protocols": "7.5.3", + "@wdio/types": "7.5.3", + "@wdio/utils": "7.5.3", "got": "^11.0.2", - "ky": "^0.29.0", "lodash.merge": "^4.6.1" + }, + "dependencies": { + "@wdio/logger": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", + "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/types": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", + "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "dev": true, + "requires": { + "got": "^11.8.1" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "webdriverio": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.16.tgz", - "integrity": "sha512-caPaEWyuD3Qoa7YkW4xCCQA4v9Pa9wmhFGPvNZh3ERtjMCNi8L/XXOdkekWNZmFh3tY0kFguBj7+fAwSY7HAGw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.25.4.tgz", + "integrity": "sha512-agkgwn2SIk5cAJ03uue1GnGZcUZUDN3W4fUMY9/VfO8bVJrPEgWg31bPguEWPu+YhEB/aBJD8ECxJ3OEKdy4qQ==", "dev": true, "requires": { "@types/aria-query": "^5.0.0", - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/repl": "7.16.14", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", + "@types/node": "^18.0.0", + "@wdio/config": "7.25.4", + "@wdio/logger": "7.19.0", + "@wdio/protocols": "7.22.0", + "@wdio/repl": "7.25.4", + "@wdio/types": "7.25.4", + "@wdio/utils": "7.25.4", "archiver": "^5.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.16.16", - "devtools-protocol": "^0.0.973690", + "devtools": "7.25.4", + "devtools-protocol": "^0.0.1061995", "fs-extra": "^10.0.0", - "get-port": "^5.1.1", "grapheme-splitter": "^1.0.2", "lodash.clonedeep": "^4.5.0", "lodash.isobject": "^3.0.2", @@ -37676,9 +18669,85 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^8.0.0", - "webdriver": "7.16.16" + "webdriver": "7.25.4" }, "dependencies": { + "@types/node": { + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", + "dev": true + }, + "@wdio/config": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.25.4.tgz", + "integrity": "sha512-vb0emDtD9FbFh/yqW6oNdo2iuhQp8XKj6GX9fyy9v4wZgg3B0HPMVJxhIfcoHz7LMBWlHSo9YdvhFI5EQHRLBA==", + "dev": true, + "requires": { + "@wdio/logger": "7.19.0", + "@wdio/types": "7.25.4", + "@wdio/utils": "7.25.4", + "deepmerge": "^4.0.0", + "glob": "^8.0.3" + } + }, + "@wdio/logger": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.19.0.tgz", + "integrity": "sha512-xR7SN/kGei1QJD1aagzxs3KMuzNxdT/7LYYx+lt6BII49+fqL/SO+5X0FDCZD0Ds93AuQvvz9eGyzrBI2FFXmQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + } + }, + "@wdio/protocols": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.22.0.tgz", + "integrity": "sha512-8EXRR+Ymdwousm/VGtW3H1hwxZ/1g1H99A1lF0U4GuJ5cFWHCd0IVE5H31Z52i8ZruouW8jueMkGZPSo2IIUSQ==", + "dev": true + }, + "@wdio/repl": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.25.4.tgz", + "integrity": "sha512-kYhj9gLsUk4HmlXLqkVre+gwbfvw9CcnrHjqIjrmMS4mR9D8zvBb5CItb3ZExfPf9jpFzIFREbCAYoE9x/kMwg==", + "dev": true, + "requires": { + "@wdio/utils": "7.25.4" + } + }, + "@wdio/types": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.25.4.tgz", + "integrity": "sha512-muvNmq48QZCvocctnbe0URq2FjJjUPIG4iLoeMmyF0AQgdbjaUkMkw3BHYNHVTbSOU9WMsr2z8alhj/I2H6NRQ==", + "dev": true, + "requires": { + "@types/node": "^18.0.0", + "got": "^11.8.1" + } + }, + "@wdio/utils": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.25.4.tgz", + "integrity": "sha512-8iwQDk+foUqSzKZKfhLxjlCKOkfRJPNHaezQoevNgnrTq/t0ek+ldZCATezb9B8jprAuP4mgS9xi22akc6RkzA==", + "dev": true, + "requires": { + "@wdio/logger": "7.19.0", + "@wdio/types": "7.25.4", + "p-iteration": "^1.1.8" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -37688,14 +18757,90 @@ "balanced-match": "^1.0.0" } }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ky": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", + "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", + "dev": true + }, "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "webdriver": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.25.4.tgz", + "integrity": "sha512-6nVDwenh0bxbtUkHASz9B8T9mB531Fn1PcQjUGj2t5dolLPn6zuK1D7XYVX40hpn6r3SlYzcZnEBs4X0az5Txg==", + "dev": true, + "requires": { + "@types/node": "^18.0.0", + "@wdio/config": "7.25.4", + "@wdio/logger": "7.19.0", + "@wdio/protocols": "7.22.0", + "@wdio/types": "7.25.4", + "@wdio/utils": "7.25.4", + "got": "^11.0.2", + "ky": "0.30.0", + "lodash.merge": "^4.6.1" + } } } }, @@ -37706,9 +18851,9 @@ "dev": true }, "webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", @@ -37716,31 +18861,31 @@ "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true }, "acorn-import-assertions": { @@ -37761,12 +18906,6 @@ "uri-js": "^4.2.2" } }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -37781,9 +18920,9 @@ } }, "webpack-bundle-analyzer": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", - "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", + "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", "dev": true, "requires": { "acorn": "^8.0.4", @@ -37798,15 +18937,9 @@ }, "dependencies": { "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "dev": true }, "ansi-styles": { @@ -37849,6 +18982,12 @@ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -37859,9 +18998,9 @@ } }, "ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "dev": true } } @@ -37923,6 +19062,66 @@ "supports-color": "^8.1.1", "through": "^2.3.8", "vinyl": "^2.2.1" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "websocket-driver": { @@ -37986,18 +19185,66 @@ "is-weakset": "^2.0.1" } }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", + "dev": true + }, "which-typed-array": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", - "integrity": "sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.8.tgz", + "integrity": "sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw==", "dev": true, "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", - "es-abstract": "^1.18.5", - "foreach": "^2.0.5", + "es-abstract": "^1.20.0", + "for-each": "^0.3.3", "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.7" + "is-typed-array": "^1.1.9" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "word-wrap": { @@ -38013,9 +19260,9 @@ "dev": true }, "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { @@ -38112,9 +19359,9 @@ "dev": true }, "yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true }, "yargs-unparser": { @@ -38135,12 +19382,6 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", diff --git a/package.json b/package.json index 9ca930c6e7e..bbea155c2b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "7.19.7", + "version": "7.37.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -35,13 +35,13 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", - "@wdio/browserstack-service": "^7.16.0", - "@wdio/cli": "^7.5.2", - "@wdio/concise-reporter": "^7.5.2", - "@wdio/local-runner": "^7.5.2", - "@wdio/mocha-framework": "^7.5.2", - "@wdio/spec-reporter": "^7.19.0", - "@wdio/sync": "^7.5.2", + "@wdio/browserstack-service": "~7.16.0", + "@wdio/cli": "~7.5.2", + "@wdio/concise-reporter": "~7.5.2", + "@wdio/local-runner": "~7.5.2", + "@wdio/mocha-framework": "~7.5.2", + "@wdio/spec-reporter": "~7.19.0", + "@wdio/sync": "~7.5.2", "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", @@ -64,7 +64,7 @@ "faker": "^5.5.3", "fs.extra": "^1.3.2", "gulp": "^4.0.0", - "gulp-clean": "^0.3.2", + "gulp-clean": "^0.4.0", "gulp-concat": "^2.6.0", "gulp-connect": "^5.7.0", "gulp-eslint": "^6.0.0", @@ -97,7 +97,7 @@ "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", - "mocha": "^5.0.0", + "mocha": "^10.0.0", "morgan": "^1.10.0", "opn": "^5.4.0", "resolve-from": "^5.0.0", @@ -105,6 +105,10 @@ "through2": "^4.0.2", "url": "^0.11.0", "url-parse": "^1.0.5", + "video.js": "^7.17.0", + "videojs-contrib-ads": "^6.9.0", + "videojs-ima": "^1.11.0", + "videojs-playlist": "^5.0.0", "webdriverio": "^7.6.1", "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", @@ -126,7 +130,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "2.4.0" + "live-connect-js": "^4.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/src/adRendering.js b/src/adRendering.js index a645ec77244..0a847d7cc25 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -23,7 +23,7 @@ export function emitAdRenderFail({ reason, message, bid, id }) { /** * Emit the AD_RENDER_SUCCEEDED event. - * + * (Note: Invocation of this function indicates that the render function did not generate an error, it does not guarantee that tracking for this event has occurred yet.) * @param doc document object that was used to `.write` the ad. Should be `null` if unavailable (e.g. for documents in * a cross-origin frame). * @param bid bid response object for the ad that was rendered diff --git a/src/adapterManager.js b/src/adapterManager.js index 4722013d5f0..c9715fe0168 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -31,10 +31,11 @@ import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; import {adunitCounter} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; -import {GdprConsentHandler, UspConsentHandler} from './consentHandler.js'; +import {GdprConsentHandler, UspConsentHandler, GppConsentHandler} from './consentHandler.js'; import * as events from './events.js'; import CONSTANTS from './constants.json'; import {useMetrics} from './utils/perfMetrics.js'; +import {auctionManager} from './auctionManager.js'; export const PARTITIONS = { CLIENT: 'client', @@ -64,15 +65,21 @@ var _analyticsRegistry = {}; function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics}) { return adUnits.reduce((result, adUnit) => { - result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode) - .reduce((bids, bid) => { - bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'nativeParams', - 'nativeOrtbRequest', - 'ortb2Imp', - 'mediaType', - 'renderer' - ])); + const bids = adUnit.bids.filter(bid => bid.bidder === bidderCode); + if (bidderCode == null && bids.length === 0 && adUnit.s2sBid != null) { + bids.push({bidder: null}); + } + result.push( + bids.reduce((bids, bid) => { + bid = Object.assign({}, bid, + {ortb2Imp: mergeDeep({}, adUnit.ortb2Imp, bid.ortb2Imp)}, + getDefinedParams(adUnit, [ + 'nativeParams', + 'nativeOrtbRequest', + 'mediaType', + 'renderer' + ]) + ); const mediaTypes = bid.mediaTypes == null ? adUnit.mediaTypes : bid.mediaTypes @@ -127,9 +134,18 @@ export const filterBidsForAdUnit = hook('sync', _filterBidsForAdUnit, 'filterBid function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { let adUnitsCopy = deepClone(adUnits); + let hasModuleBids = false; adUnitsCopy.forEach((adUnit) => { // filter out client side bids + const s2sBids = adUnit.bids.filter((b) => b.module === 'pbsBidAdapter' && b.params?.configName === s2sConfig.configName); + if (s2sBids.length === 1) { + adUnit.s2sBid = s2sBids[0]; + hasModuleBids = true; + adUnit.ortb2Imp = mergeDeep({}, adUnit.s2sBid.ortb2Imp, adUnit.ortb2Imp); + } else if (s2sBids.length > 1) { + logWarn('Multiple "module" bids for the same s2s configuration; all will be ignored', s2sBids); + } adUnit.bids = filterBidsForAdUnit(adUnit.bids, s2sConfig) .map((bid) => { bid.bid_id = getUniqueIdentifierStr(); @@ -139,9 +155,9 @@ function getAdUnitCopyForPrebidServer(adUnits, s2sConfig) { // don't send empty requests adUnitsCopy = adUnitsCopy.filter(adUnit => { - return adUnit.bids.length !== 0; + return adUnit.bids.length !== 0 || adUnit.s2sBid != null; }); - return adUnitsCopy; + return {adUnits: adUnitsCopy, hasModuleBids}; } function getAdUnitCopyForClientAdapters(adUnits) { @@ -160,6 +176,7 @@ function getAdUnitCopyForClientAdapters(adUnits) { export let gdprDataHandler = new GdprConsentHandler(); export let uspDataHandler = new UspConsentHandler(); +export let gppDataHandler = new GppConsentHandler(); export let coppaDataHandler = { getCoppa: function() { @@ -242,11 +259,12 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a _s2sConfigs.forEach(s2sConfig => { if (s2sConfig && s2sConfig.enabled) { - let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); + let {adUnits: adUnitsS2SCopy, hasModuleBids} = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); // uniquePbsTid is so we know which server to send which bids to during the callBids function let uniquePbsTid = generateUUID(); - serverBidders.forEach(bidderCode => { + + (serverBidders.length === 0 && hasModuleBids ? [null] : serverBidders).forEach(bidderCode => { const bidderRequestId = getUniqueIdentifierStr(); const metrics = auctionMetrics.fork(); const bidderRequest = addOrtb2({ @@ -277,7 +295,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a bidRequests.forEach(request => { if (request.adUnitsS2SCopy === undefined) { - request.adUnitsS2SCopy = adUnitsS2SCopy.filter(adUnitCopy => adUnitCopy.bids.length > 0); + request.adUnitsS2SCopy = adUnitsS2SCopy.filter(au => au.bids.length > 0 || au.s2sBid != null); } }); } @@ -308,17 +326,17 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } }); - if (gdprDataHandler.getConsentData()) { - bidRequests.forEach(bidRequest => { + bidRequests.forEach(bidRequest => { + if (gdprDataHandler.getConsentData()) { bidRequest['gdprConsent'] = gdprDataHandler.getConsentData(); - }); - } - - if (uspDataHandler.getConsentData()) { - bidRequests.forEach(bidRequest => { + } + if (uspDataHandler.getConsentData()) { bidRequest['uspConsent'] = uspDataHandler.getConsentData(); - }); - } + } + if (gppDataHandler.getConsentData()) { + bidRequest['gppConsent'] = gppDataHandler.getConsentData(); + } + }); bidRequests.forEach(bidRequest => { config.runWithBidder(bidRequest.bidderCode, () => { @@ -553,19 +571,30 @@ adapterManager.getAnalyticsAdapter = function(code) { return _analyticsRegistry[code]; } -function tryCallBidderMethod(bidder, method, param) { +function getBidderMethod(bidder, method) { + const adapter = _bidderRegistry[bidder]; + const spec = adapter?.getSpec && adapter.getSpec(); + if (spec && spec[method] && typeof spec[method] === 'function') { + return [spec, spec[method]] + } +} + +function invokeBidderMethod(bidder, method, spec, fn, ...params) { try { - const adapter = _bidderRegistry[bidder]; - const spec = adapter.getSpec(); - if (spec && spec[method] && typeof spec[method] === 'function') { - logInfo(`Invoking ${bidder}.${method}`); - config.runWithBidder(bidder, bind.call(spec[method], spec, param)); - } + logInfo(`Invoking ${bidder}.${method}`); + config.runWithBidder(bidder, fn.bind(spec, ...params)); } catch (e) { logWarn(`Error calling ${method} of ${bidder}`); } } +function tryCallBidderMethod(bidder, method, param) { + const target = getBidderMethod(bidder, method); + if (target != null) { + invokeBidderMethod(bidder, method, ...target, param); + } +} + adapterManager.callTimedOutBidders = function(adUnits, timedOutBidders, cbTimeout) { timedOutBidders = timedOutBidders.map((timedOutBidder) => { // Adding user configured params & timeout to timeout event data @@ -600,4 +629,41 @@ adapterManager.callBidderError = function(bidder, error, bidderRequest) { tryCallBidderMethod(bidder, 'onBidderError', param); }; +function resolveAlias(alias) { + const seen = new Set(); + while (_aliasRegistry.hasOwnProperty(alias) && !seen.has(alias)) { + seen.add(alias); + alias = _aliasRegistry[alias]; + } + return alias; +} +/** + * Ask every adapter to delete PII. + * See https://github.com/prebid/Prebid.js/issues/9081 + */ +adapterManager.callDataDeletionRequest = hook('sync', function (...args) { + const method = 'onDataDeletionRequest'; + Object.keys(_bidderRegistry) + .filter((bidder) => !_aliasRegistry.hasOwnProperty(bidder)) + .forEach(bidder => { + const target = getBidderMethod(bidder, method); + if (target != null) { + const bidderRequests = auctionManager.getBidsRequested().filter((br) => + resolveAlias(br.bidderCode) === bidder + ); + invokeBidderMethod(bidder, method, ...target, bidderRequests, ...args); + } + }); + Object.entries(_analyticsRegistry).forEach(([name, entry]) => { + const fn = entry?.adapter?.[method]; + if (typeof fn === 'function') { + try { + fn.apply(entry.adapter, args); + } catch (e) { + logError(`error calling ${method} of ${name}`, e); + } + } + }); +}); + export default adapterManager; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index c60f4b8a015..55c84e57062 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -202,6 +202,8 @@ export function newBidder(spec) { adUnitCodesHandled[adUnitCode] = true; if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnitCode, bid))) { addBidResponse(adUnitCode, bid); + } else { + addBidResponse.reject(adUnitCode, bid, CONSTANTS.REJECTION_REASON.INVALID) } } @@ -212,7 +214,7 @@ export function newBidder(spec) { done(); config.runWithBidder(spec.code, () => { events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); - registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent); + registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent, bidderRequest.gppConsent); }); } @@ -262,6 +264,7 @@ export function newBidder(spec) { bid.adapterCode = bidRequest.bidder; if (isInvalidAlternateBidder(bid.bidderCode, bidRequest.bidder)) { logWarn(`${bid.bidderCode} is not a registered partner or known bidder of ${bidRequest.bidder}, hence continuing without bid. If you wish to support this bidder, please mark allowAlternateBidderCodes as true in bidderSettings.`); + addBidResponse.reject(bidRequest.adUnitCode, bid, CONSTANTS.REJECTION_REASON.BIDDER_DISALLOWED) return; } // creating a copy of original values as cpm and currency are modified later @@ -272,6 +275,7 @@ export function newBidder(spec) { addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bid.requestId}. Ignoring.`); + addBidResponse.reject(null, bid, CONSTANTS.REJECTION_REASON.INVALID_REQUEST_ID); } }, onCompletion: afterAllResponses, @@ -291,8 +295,8 @@ export function newBidder(spec) { return false; } - function registerSyncs(responses, gdprConsent, uspConsent) { - registerSyncInner(spec, responses, gdprConsent, uspConsent); + function registerSyncs(responses, gdprConsent, uspConsent, gppConsent) { + registerSyncInner(spec, responses, gdprConsent, uspConsent, gppConsent); } function filterAndWarn(bid) { @@ -444,14 +448,14 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe }) }, 'processBidderRequests') -export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent) { +export const registerSyncInner = hook('async', function(spec, responses, gdprConsent, uspConsent, gppConsent) { const aliasSyncEnabled = config.getConfig('userSync.aliasSyncEnabled'); if (spec.getUserSyncs && (aliasSyncEnabled || !adapterManager.aliasRegistry[spec.code])) { let filterConfig = config.getConfig('userSync.filterSettings'); let syncs = spec.getUserSyncs({ iframeEnabled: !!(filterConfig && (filterConfig.iframe || filterConfig.all)), pixelEnabled: !!(filterConfig && (filterConfig.image || filterConfig.all)), - }, responses, gdprConsent, uspConsent); + }, responses, gdprConsent, uspConsent, gppConsent); if (syncs) { if (!Array.isArray(syncs)) { syncs = [syncs]; diff --git a/src/adloader.js b/src/adloader.js index 6b7427d3e52..f0b7f7f3e8c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -19,7 +19,10 @@ const _approvedLoadExternalJSList = [ 'inskin', 'hadron', 'medianet', - 'improvedigital' + 'improvedigital', + 'aaxBlockmeter', + 'confiant', + 'arcspan' ] /** diff --git a/src/auction.js b/src/auction.js index 81280e0833c..41e6fe3565b 100644 --- a/src/auction.js +++ b/src/auction.js @@ -58,26 +58,42 @@ */ import { - flatten, timestamp, adUnitsFilter, deepAccess, getValue, parseUrl, generateUUID, - logMessage, bind, logError, logInfo, logWarn, isEmpty, _each, isFn, isEmptyStr + _each, + adUnitsFilter, + bind, + deepAccess, + flatten, + generateUUID, + getValue, + isEmpty, + isEmptyStr, + isFn, + logError, + logInfo, + logMessage, + logWarn, + parseUrl, + timestamp } from './utils.js'; -import { getPriceBucketString } from './cpmBucketManager.js'; -import { getNativeTargeting } from './native.js'; -import { getCacheUrl, store } from './videoCache.js'; -import { Renderer } from './Renderer.js'; -import { config } from './config.js'; -import { userSync } from './userSync.js'; -import { hook } from './hook.js'; +import {getPriceBucketString} from './cpmBucketManager.js'; +import {getNativeTargeting, toLegacyResponse} from './native.js'; +import {getCacheUrl, store} from './videoCache.js'; +import {Renderer} from './Renderer.js'; +import {config} from './config.js'; +import {userSync} from './userSync.js'; +import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; -import { OUTSTREAM } from './video.js'; -import { VIDEO } from './mediaTypes.js'; +import {OUTSTREAM} from './video.js'; +import {VIDEO} from './mediaTypes.js'; import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; -import * as events from './events.js' +import * as events from './events.js'; import adapterManager from './adapterManager.js'; import CONSTANTS from './constants.json'; import {GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; +import {createBid} from './bidfactory.js'; +import {adjustCpm} from './utils/cpm.js'; const { syncUsers } = userSync; @@ -119,24 +135,26 @@ export function resetAuctionState() { */ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId, ortb2Fragments, metrics}) { metrics = useMetrics(metrics); - let _adUnits = adUnits; - let _labels = labels; - let _adUnitCodes = adUnitCodes; + const _adUnits = adUnits; + const _labels = labels; + const _adUnitCodes = adUnitCodes; + const _auctionId = auctionId || generateUUID(); + const _timeout = cbTimeout; + const _timelyBidders = new Set(); + let _bidsRejected = []; + let _callback = callback; let _bidderRequests = []; let _bidsReceived = []; let _noBids = []; + let _winningBids = []; let _auctionStart; let _auctionEnd; - let _auctionId = auctionId || generateUUID(); - let _auctionStatus; - let _callback = callback; let _timer; - let _timeout = cbTimeout; - let _winningBids = []; - let _timelyBidders = new Set(); + let _auctionStatus; function addBidRequests(bidderRequests) { _bidderRequests = _bidderRequests.concat(bidderRequests); } function addBidReceived(bidsReceived) { _bidsReceived = _bidsReceived.concat(bidsReceived); } + function addBidRejected(bidsRejected) { _bidsRejected = _bidsRejected.concat(bidsRejected); } function addNoBid(noBid) { _noBids = _noBids.concat(noBid); } function getProperties() { @@ -151,6 +169,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a bidderRequests: _bidderRequests, noBids: _noBids, bidsReceived: _bidsReceived, + bidsRejected: _bidsRejected, winningBids: _winningBids, timeout: _timeout, metrics: metrics @@ -352,6 +371,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a return { addBidReceived, + addBidRejected, addNoBid, executeCallback, callBids, @@ -372,7 +392,14 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a }; } -export const addBidResponse = hook('sync', function(adUnitCode, bid) { +/** + * Hook into this to intercept bids before they are added to an auction. + * + * @param adUnitCode + * @param bid + * @param {function(String)} reject: a function that, when called, rejects `bid` with the given reason. + */ +export const addBidResponse = hook('sync', function(adUnitCode, bid, reject) { this.dispatch.call(null, adUnitCode, bid); }, 'addBidResponse'); @@ -425,20 +452,54 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM } } - function handleBidResponse(adUnitCode, bid) { + function handleBidResponse(adUnitCode, bid, handler) { bidResponseMap[bid.requestId] = true; - + addCommonResponseProperties(bid, adUnitCode) outstandingBidsAdded++; - let auctionId = auctionInstance.getAuctionId(); + return handler(afterBidAdded); + } - let bidResponse = getPreparedBidForAuction({adUnitCode, bid, auctionId}); + function acceptBidResponse(adUnitCode, bid) { + handleBidResponse(adUnitCode, bid, (done) => { + let bidResponse = getPreparedBidForAuction(bid); - if (bidResponse.mediaType === 'video') { - tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded); - } else { - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); - } + if (bidResponse.mediaType === VIDEO) { + tryAddVideoBid(auctionInstance, bidResponse, done); + } else { + if (FEATURES.NATIVE && bidResponse.native != null && typeof bidResponse.native === 'object') { + // NOTE: augment bidResponse.native even if bidResponse.mediaType !== NATIVE; it's possible + // to treat banner responses as native + addLegacyFieldsIfNeeded(bidResponse); + } + addBidToAuction(auctionInstance, bidResponse); + done(); + } + }); + } + + function rejectBidResponse(adUnitCode, bid, reason) { + return handleBidResponse(adUnitCode, bid, (done) => { + // return a "NO_BID" replacement that the caller can decide to continue with + // TODO: remove this in v8; see https://github.com/prebid/Prebid.js/issues/8956 + const noBid = createBid(CONSTANTS.STATUS.NO_BID, bid.getIdentifiers?.()); + Object.assign(noBid, Object.fromEntries(Object.entries(bid).filter(([k]) => !noBid.hasOwnProperty(k) && ![ + 'ad', + 'adUrl', + 'vastXml', + 'vastUrl', + 'native', + ].includes(k)))); + noBid.status = CONSTANTS.BID_STATUS.BID_REJECTED; + noBid.cpm = 0; + + bid.rejectionReason = reason; + logWarn(`Bid from ${bid.bidder || 'unknown bidder'} was rejected: ${reason}`, bid) + events.emit(CONSTANTS.EVENTS.BID_REJECTED, bid); + auctionInstance.addBidRejected(bid); + done(); + + return noBid; + }) } function adapterDone() { @@ -470,12 +531,24 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM } return { - addBidResponse: function (adUnit, bid) { - const bidderRequest = index.getBidderRequest(bid); - waitFor((bidderRequest && bidderRequest.bidderRequestId) || '', addBidResponse.call({ - dispatch: handleBidResponse, - }, adUnit, bid)); - }, + addBidResponse: (function () { + function addBid(adUnitCode, bid) { + const bidderRequest = index.getBidderRequest(bid); + waitFor((bidderRequest && bidderRequest.bidderRequestId) || '', addBidResponse.call({ + dispatch: acceptBidResponse, + }, adUnitCode, bid, (() => { + let rejection; + return (reason) => { + if (rejection == null) { + rejection = rejectBidResponse(adUnitCode, bid, reason); + } + return rejection; + } + })())); + } + addBid.reject = rejectBidResponse; + return addBid; + })(), adapterDone: function () { guard(this, adapterDone.bind(this)) } @@ -492,9 +565,7 @@ export function doCallbacksIfTimedout(auctionInstance, bidResponse) { export function addBidToAuction(auctionInstance, bidResponse) { setupBidTargeting(bidResponse); - const metrics = useMetrics(bidResponse.metrics); - metrics.timeSince('addBidResponse', 'addBidResponse.total'); - + useMetrics(bidResponse.metrics).timeSince('addBidResponse', 'addBidResponse.total'); events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); auctionInstance.addBidReceived(bidResponse); @@ -528,6 +599,17 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au } } +// Native bid response might be in ortb2 format - adds legacy field for backward compatibility +const addLegacyFieldsIfNeeded = (bidResponse) => { + const nativeOrtbRequest = auctionManager.index.getAdUnit(bidResponse)?.nativeOrtbRequest; + const nativeOrtbResponse = bidResponse.native?.ortb + + if (nativeOrtbRequest && nativeOrtbResponse) { + const legacyResponse = toLegacyResponse(nativeOrtbResponse, nativeOrtbRequest); + Object.assign(bidResponse.native, legacyResponse); + } +} + const storeInCache = (batch) => { store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { cacheIds.forEach((cacheId, i) => { @@ -595,35 +677,46 @@ export const callPrebidCache = hook('async', function(auctionInstance, bidRespon batchAndStore(auctionInstance, bidResponse, afterBidAdded); }, 'callPrebidCache'); -// Postprocess the bids so that all the universal properties exist, no matter which bidder they came from. -// This should be called before addBidToAuction(). -function getPreparedBidForAuction({adUnitCode, bid, auctionId}, {index = auctionManager.index} = {}) { - const bidderRequest = index.getBidderRequest(bid); - const start = (bidderRequest && bidderRequest.start) || bid.requestTimestamp; - - let bidObject = Object.assign({}, bid, { - auctionId, - responseTimestamp: timestamp(), - requestTimestamp: start, - cpm: parseFloat(bid.cpm) || 0, - bidder: bid.bidderCode, +/** + * Augment `bidResponse` with properties that are common across all bids - including rejected bids. + * + */ +function addCommonResponseProperties(bidResponse, adUnitCode, {index = auctionManager.index} = {}) { + const bidderRequest = index.getBidderRequest(bidResponse); + const adUnit = index.getAdUnit(bidResponse); + const start = (bidderRequest && bidderRequest.start) || bidResponse.requestTimestamp; + + Object.assign(bidResponse, { + responseTimestamp: bidResponse.responseTimestamp || timestamp(), + requestTimestamp: bidResponse.requestTimestamp || start, + cpm: parseFloat(bidResponse.cpm) || 0, + bidder: bidResponse.bidder || bidResponse.bidderCode, adUnitCode }); - bidObject.timeToRespond = bidObject.responseTimestamp - bidObject.requestTimestamp; + if (adUnit?.ttlBuffer != null) { + bidResponse.ttlBuffer = adUnit.ttlBuffer; + } + + bidResponse.timeToRespond = bidResponse.responseTimestamp - bidResponse.requestTimestamp; +} +/** + * Add additional bid response properties that are universal for all _accepted_ bids. + */ +function getPreparedBidForAuction(bid, {index = auctionManager.index} = {}) { // Let listeners know that now is the time to adjust the bid, if they want to. // // CAREFUL: Publishers rely on certain bid properties to be available (like cpm), // but others to not be set yet (like priceStrings). See #1372 and #1389. - events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bidObject); + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bid); // a publisher-defined renderer can be used to render bids - const adUnitRenderer = index.getAdUnit(bidObject).renderer; + const bidRenderer = index.getBidRequest(bid)?.renderer || index.getAdUnit(bid).renderer; // a publisher can also define a renderer for a mediaType - const bidObjectMediaType = bidObject.mediaType; - const mediaTypes = index.getMediaTypes(bidObject); + const bidObjectMediaType = bid.mediaType; + const mediaTypes = index.getMediaTypes(bid); const bidMediaType = mediaTypes && mediaTypes[bidObjectMediaType]; var mediaTypeRenderer = bidMediaType && bidMediaType.renderer; @@ -633,31 +726,31 @@ function getPreparedBidForAuction({adUnitCode, bid, auctionId}, {index = auction // the renderer for the mediaType takes precendence if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { renderer = mediaTypeRenderer; - } else if (adUnitRenderer && adUnitRenderer.url && adUnitRenderer.render && !(adUnitRenderer.backupOnly === true && bid.renderer)) { - renderer = adUnitRenderer; + } else if (bidRenderer && bidRenderer.url && bidRenderer.render && !(bidRenderer.backupOnly === true && bid.renderer)) { + renderer = bidRenderer; } if (renderer) { // be aware, an adapter could already have installed the bidder, in which case this overwrite's the existing adapter - bidObject.renderer = Renderer.install({ url: renderer.url, config: renderer.options });// rename options to config, to make it consistent? - bidObject.renderer.setRender(renderer.render); + bid.renderer = Renderer.install({ url: renderer.url, config: renderer.options });// rename options to config, to make it consistent? + bid.renderer.setRender(renderer.render); } // Use the config value 'mediaTypeGranularity' if it has been defined for mediaType, else use 'customPriceBucket' const mediaTypeGranularity = getMediaTypeGranularity(bid.mediaType, mediaTypes, config.getConfig('mediaTypePriceGranularity')); const priceStringsObj = getPriceBucketString( - bidObject.cpm, + bid.cpm, (typeof mediaTypeGranularity === 'object') ? mediaTypeGranularity : config.getConfig('customPriceBucket'), config.getConfig('currency.granularityMultiplier') ); - bidObject.pbLg = priceStringsObj.low; - bidObject.pbMg = priceStringsObj.med; - bidObject.pbHg = priceStringsObj.high; - bidObject.pbAg = priceStringsObj.auto; - bidObject.pbDg = priceStringsObj.dense; - bidObject.pbCg = priceStringsObj.custom; - - return bidObject; + bid.pbLg = priceStringsObj.low; + bid.pbMg = priceStringsObj.med; + bid.pbHg = priceStringsObj.high; + bid.pbAg = priceStringsObj.auto; + bid.pbDg = priceStringsObj.dense; + bid.pbCg = priceStringsObj.custom; + + return bid; } function setupBidTargeting(bidObject) { @@ -736,6 +829,16 @@ export const getAdvertiserDomain = () => { } } +/** + * This function returns a function to get the primary category id from bid response meta + * @returns {function} + */ +export const getPrimaryCatId = () => { + return (bid) => { + return (bid.meta && bid.meta.primaryCatId) ? bid.meta.primaryCatId : ''; + } +} + // factory for key value objs function createKeyVal(key, value) { return { @@ -761,6 +864,7 @@ function defaultAdserverTargeting() { createKeyVal(TARGETING_KEYS.SOURCE, 'source'), createKeyVal(TARGETING_KEYS.FORMAT, 'mediaType'), createKeyVal(TARGETING_KEYS.ADOMAIN, getAdvertiserDomain()), + createKeyVal(TARGETING_KEYS.ACAT, getPrimaryCatId()), ] } @@ -773,7 +877,6 @@ function defaultAdserverTargeting() { export function getStandardBidderSettings(mediaType, bidderCode) { const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; const standardSettings = Object.assign({}, bidderSettings.settingsFor(null)); - if (!standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = defaultAdserverTargeting(); } @@ -869,17 +972,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { } export function adjustBids(bid) { - let code = bid.bidderCode; - let bidPriceAdjusted = bid.cpm; - const bidCpmAdjustment = bidderSettings.get(code || null, 'bidCpmAdjustment'); - - if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { - try { - bidPriceAdjusted = bidCpmAdjustment(bid.cpm, Object.assign({}, bid)); - } catch (e) { - logError('Error during bid adjustment', 'bidmanager.js', e); - } - } + let bidPriceAdjusted = adjustCpm(bid.cpm, bid); if (bidPriceAdjusted >= 0) { bid.cpm = bidPriceAdjusted; diff --git a/src/consentHandler.js b/src/consentHandler.js index 861a9894a2c..b1b2a04c043 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -1,6 +1,14 @@ import {isStr, timestamp} from './utils.js'; import {defer, GreedyPromise} from './utils/promise.js'; +/** + * Placeholder gvlid for when vendor consent is not required. When this value is used as gvlid, the gdpr + * enforcement module will take it to mean "vendor consent was given". + * + * see https://github.com/prebid/Prebid.js/issues/8161 + */ +export const VENDORLESS_GVLID = Object.freeze({}); + export class ConsentHandler { #enabled; #data; @@ -99,3 +107,14 @@ export class GdprConsentHandler extends ConsentHandler { } } } + +export class GppConsentHandler extends ConsentHandler { + getConsentMeta() { + const consentData = this.getConsentData(); + if (consentData && this.generatedTime) { + return { + generatedAt: this.generatedTime, + } + } + } +} diff --git a/src/constants.json b/src/constants.json index ad57fb45c4f..e33d65f3fb1 100644 --- a/src/constants.json +++ b/src/constants.json @@ -29,6 +29,7 @@ "BID_TIMEOUT": "bidTimeout", "BID_REQUESTED": "bidRequested", "BID_RESPONSE": "bidResponse", + "BID_REJECTED": "bidRejected", "NO_BID": "noBid", "BID_WON": "bidWon", "BIDDER_DONE": "bidderDone", @@ -75,7 +76,8 @@ "UUID": "hb_uuid", "CACHE_ID": "hb_cache_id", "CACHE_HOST": "hb_cache_host", - "ADOMAIN": "hb_adomain" + "ADOMAIN": "hb_adomain", + "ACAT": "hb_acat" }, "DEFAULT_TARGETING_KEYS": { "BIDDER": "hb_bidder", @@ -119,6 +121,13 @@ "RENDERED": "rendered", "BID_REJECTED": "bidRejected" }, + "REJECTION_REASON": { + "INVALID": "Bid has missing or invalid properties", + "INVALID_REQUEST_ID": "Invalid request ID", + "BIDDER_DISALLOWED": "Bidder code is not allowed by allowedAlternateBidderCodes / allowUnknownBidderCodes", + "FLOOR_NOT_MET": "Bid does not meet price floor", + "CANNOT_CONVERT_CURRENCY": "Unable to convert currency" + }, "PREBID_NATIVE_DATA_KEYS_TO_ORTB": { "body": "desc", "body2": "desc2", diff --git a/src/events.js b/src/events.js index e675ffef7a9..62f8c070deb 100644 --- a/src/events.js +++ b/src/events.js @@ -133,6 +133,10 @@ const _public = (function () { return _handlers; }; + _public.addEvents = function (events) { + allEvents = allEvents.concat(events); + } + /** * This method can return a copy of all the events fired * @return {Array} array of events fired @@ -152,4 +156,8 @@ const _public = (function () { utils._setEventEmitter(_public.emit.bind(_public)); -export const {on, off, get, getEvents, emit} = _public; +export const {on, off, get, getEvents, emit, addEvents} = _public; + +export function clearEvents() { + eventsFired.length = 0; +} diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js new file mode 100644 index 00000000000..fc50a057378 --- /dev/null +++ b/src/fpd/enrichment.js @@ -0,0 +1,94 @@ +import {hook} from '../hook.js'; +import {getRefererInfo, parseDomain} from '../refererDetection.js'; +import {findRootDomain} from './rootDomain.js'; +import {deepSetValue, getDefinedParams, getDNT, getWindowSelf, getWindowTop, mergeDeep} from '../utils.js'; +import {config} from '../config.js'; +import {getHighEntropySUA, getLowEntropySUA} from '../../libraries/fpd/sua.js'; +import {GreedyPromise} from '../utils/promise.js'; + +export const dep = { + getRefererInfo, + findRootDomain, + getWindowTop, + getWindowSelf, + getHighEntropySUA, + getLowEntropySUA, +}; + +/** + * Enrich an ortb2 object with first party data. + * @param {Promise[{}]} fpd: a promise to an ortb2 object. + * @returns: {Promise[{}]}: a promise to an enriched ortb2 object. + */ +export const enrichFPD = hook('sync', (fpd) => { + return GreedyPromise.all([fpd, getSUA().catch(() => null)]) + .then(([ortb2, sua]) => { + Object.entries(ENRICHMENTS).forEach(([section, getEnrichments]) => { + const data = getEnrichments(); + if (data && Object.keys(data).length > 0) { + ortb2[section] = mergeDeep({}, data, ortb2[section]); + } + }); + if (sua) { + deepSetValue(ortb2, 'device.sua', Object.assign({}, sua, ortb2.device.sua)); + } + return ortb2; + }); +}); + +function winFallback(fn) { + try { + return fn(dep.getWindowTop()); + } catch (e) { + return fn(dep.getWindowSelf()); + } +} + +function getSUA() { + const hints = config.getConfig('firstPartyData.uaHints'); + return Array.isArray(hints) && hints.length === 0 + ? GreedyPromise.resolve(dep.getLowEntropySUA()) + : dep.getHighEntropySUA(hints); +} + +const ENRICHMENTS = { + site() { + const ri = dep.getRefererInfo(); + const domain = parseDomain(ri.page, {noLeadingWww: true}); + const keywords = winFallback((win) => win.document.querySelector('meta[name=\'keywords\']')) + ?.content?.replace?.(/\s/g, ''); + return (site => getDefinedParams(site, Object.keys(site)))({ + page: ri.page, + ref: ri.ref, + domain, + keywords, + publisher: { + domain: dep.findRootDomain(domain) + } + }); + }, + device() { + return winFallback((win) => { + const w = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; + const h = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; + return { + w, + h, + dnt: getDNT() ? 1 : 0, + ua: win.navigator.userAgent, + language: win.navigator.language.split('-').shift(), + }; + }) + }, + regs() { + const regs = {}; + if (winFallback((win) => win.navigator.globalPrivacyControl)) { + deepSetValue(regs, 'ext.gpc', 1); + } + const coppa = config.getConfig('coppa'); + if (typeof coppa === 'boolean') { + regs.coppa = coppa ? 1 : 0; + } + return regs; + } +}; diff --git a/src/fpd/rootDomain.js b/src/fpd/rootDomain.js new file mode 100644 index 00000000000..4095613672f --- /dev/null +++ b/src/fpd/rootDomain.js @@ -0,0 +1,57 @@ +import {memoize, timestamp} from '../utils.js'; +import {getCoreStorageManager} from '../storageManager.js'; + +export const coreStorage = getCoreStorageManager(); + +/** + * Find the root domain by testing for the topmost domain that will allow setting cookies. + */ + +export const findRootDomain = memoize(function findRootDomain(fullDomain = window.location.host) { + if (!coreStorage.cookiesAreEnabled()) { + return fullDomain; + } + + const domainParts = fullDomain.split('.'); + if (domainParts.length === 2) { + return fullDomain; + } + let rootDomain; + let continueSearching; + let startIndex = -2; + const TEST_COOKIE_NAME = `_rdc${Date.now()}`; + const TEST_COOKIE_VALUE = 'writeable'; + do { + rootDomain = domainParts.slice(startIndex).join('.'); + let expirationDate = new Date(timestamp() + 10 * 1000).toUTCString(); + + // Write a test cookie + coreStorage.setCookie( + TEST_COOKIE_NAME, + TEST_COOKIE_VALUE, + expirationDate, + 'Lax', + rootDomain, + undefined + ); + + // See if the write was successful + const value = coreStorage.getCookie(TEST_COOKIE_NAME, undefined); + if (value === TEST_COOKIE_VALUE) { + continueSearching = false; + // Delete our test cookie + coreStorage.setCookie( + TEST_COOKIE_NAME, + '', + 'Thu, 01 Jan 1970 00:00:01 GMT', + undefined, + rootDomain, + undefined + ); + } else { + startIndex += -1; + continueSearching = Math.abs(startIndex) <= domainParts.length; + } + } while (continueSearching); + return rootDomain; +}); diff --git a/src/native.js b/src/native.js index 022ece457f5..25f8c38cb30 100644 --- a/src/native.js +++ b/src/native.js @@ -23,7 +23,7 @@ export const NATIVE_TARGETING_KEYS = Object.keys(CONSTANTS.NATIVE_KEYS).map( key => CONSTANTS.NATIVE_KEYS[key] ); -const IMAGE = { +export const IMAGE = { ortb: { ver: '1.2', assets: [ @@ -385,26 +385,14 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { return keyValues; } -const getNativeRequest = (bidResponse) => auctionManager.index.getAdUnit(bidResponse)?.nativeOrtbRequest; - -function assetsMessage(data, adObject, keys, {getNativeReq = getNativeRequest} = {}) { +function assetsMessage(data, adObject, keys) { const message = { message: 'assetResponse', adId: data.adId, }; - // Pass to Prebid Universal Creative all assets, the legacy ones + the ortb ones (under ortb property) - const ortbRequest = getNativeReq(adObject); let nativeResp = adObject.native; - const ortbResponse = adObject.native?.ortb; - let legacyResponse = {}; - if (ortbRequest && ortbResponse) { - legacyResponse = toLegacyResponse(ortbResponse, ortbRequest); - nativeResp = { - ...adObject.native, - ...legacyResponse - }; - } + if (adObject.native.ortb) { message.ortb = adObject.native.ortb; } @@ -435,13 +423,13 @@ function assetsMessage(data, adObject, keys, {getNativeReq = getNativeRequest} = * Constructs a message object containing asset values for each of the * requested data keys. */ -export function getAssetMessage(data, adObject, {getNativeReq = getNativeRequest} = {}) { +export function getAssetMessage(data, adObject) { const keys = data.assets.map((k) => getKeyByValue(CONSTANTS.NATIVE_KEYS, k)); - return assetsMessage(data, adObject, keys, {getNativeReq}); + return assetsMessage(data, adObject, keys); } -export function getAllAssetsMessage(data, adObject, {getNativeReq = getNativeRequest} = {}) { - return assetsMessage(data, adObject, null, {getNativeReq}); +export function getAllAssetsMessage(data, adObject) { + return assetsMessage(data, adObject, null); } /** @@ -749,7 +737,7 @@ export function toOrtbNativeResponse(legacyResponse, ortbRequest) { * @param {*} ortbRequest the ortb request, useful to match ids. * @returns an object containing the response in legacy native format: { title: "this is a title", image: ... } */ -function toLegacyResponse(ortbResponse, ortbRequest) { +export function toLegacyResponse(ortbResponse, ortbRequest) { const legacyResponse = {}; const requestAssets = ortbRequest?.assets || []; legacyResponse.clickUrl = ortbResponse.link.url; @@ -764,6 +752,29 @@ function toLegacyResponse(ortbResponse, ortbRequest) { legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset.data.type]]] = asset.data.value; } } + + // Handle trackers + legacyResponse.impressionTrackers = []; + let jsTrackers = []; + + if (ortbRequest?.imptrackers) { + legacyResponse.impressionTrackers.push(...ortbRequest.imptrackers); + } + for (const eventTracker of ortbResponse?.eventtrackers || []) { + if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.img) { + legacyResponse.impressionTrackers.push(eventTracker.url); + } + if (eventTracker.event === TRACKER_EVENTS.impression && eventTracker.method === TRACKER_METHODS.js) { + jsTrackers.push(eventTracker.url); + } + } + + jsTrackers = jsTrackers.map(url => ``); + if (ortbResponse?.jstracker) { jsTrackers.push(ortbResponse.jstracker); } + if (jsTrackers.length) { + legacyResponse.javascriptTrackers = jsTrackers.join('\n'); + } + return legacyResponse; } diff --git a/src/pbjsORTB.js b/src/pbjsORTB.js new file mode 100644 index 00000000000..cc5c1c5bdd9 --- /dev/null +++ b/src/pbjsORTB.js @@ -0,0 +1,35 @@ +export const PROCESSOR_TYPES = ['request', 'imp', 'bidResponse', 'response']; +export const PROCESSOR_DIALECTS = ['default', 'pbs']; +export const [REQUEST, IMP, BID_RESPONSE, RESPONSE] = PROCESSOR_TYPES; +export const [DEFAULT, PBS] = PROCESSOR_DIALECTS; + +const types = new Set(PROCESSOR_TYPES); + +export function processorRegistry() { + const processors = {}; + + return { + registerOrtbProcessor({type, name, fn, priority = 0, dialects = [DEFAULT]}) { + if (!types.has(type)) { + throw new Error(`ORTB processor type must be one of: ${PROCESSOR_TYPES.join(', ')}`) + } + dialects.forEach(dialect => { + if (!processors.hasOwnProperty(dialect)) { + processors[dialect] = {}; + } + if (!processors[dialect].hasOwnProperty(type)) { + processors[dialect][type] = {}; + } + processors[dialect][type][name] = { + priority, + fn + } + }) + }, + getProcessors(dialect) { + return processors[dialect] || {}; + } + } +} + +export const {registerOrtbProcessor, getProcessors} = processorRegistry(); diff --git a/src/prebid.js b/src/prebid.js index 18826fc7205..9ad72409990 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -36,7 +36,7 @@ import {listenMessagesFromCreative} from './secureCreatives.js'; import {userSync} from './userSync.js'; import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; -import {filters, targeting} from './targeting.js'; +import {isBidUsable, targeting} from './targeting.js'; import {hook, wrapHook} from './hook.js'; import {loadSession} from './debugging.js'; import {includes} from './polyfill.js'; @@ -45,10 +45,12 @@ import {executeRenderer, isRendererRequired} from './Renderer.js'; import {createBid} from './bidfactory.js'; import {storageCallbacks} from './storageManager.js'; import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js'; -import {default as adapterManager, gdprDataHandler, getS2SBidderSet, uspDataHandler} from './adapterManager.js'; +import {default as adapterManager, gdprDataHandler, getS2SBidderSet, gppDataHandler, uspDataHandler} from './adapterManager.js'; import CONSTANTS from './constants.json'; import * as events from './events.js'; import {newMetrics, useMetrics} from './utils/perfMetrics.js'; +import {defer, GreedyPromise} from './utils/promise.js'; +import {enrichFPD} from './fpd/enrichment.js'; const $$PREBID_GLOBAL$$ = getGlobal(); const { triggerUserSyncs } = userSync; @@ -296,8 +298,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode = function (adunitCode) { if (adunitCode) { const bid = auctionManager.getAllBidsForAdUnitCode(adunitCode) - .filter(filters.isUnusedBid) - .filter(filters.isBidNotExpired) + .filter(isBidUsable) return bid.length ? bid.reduce(getHighestCpm) : {} } else { @@ -335,6 +336,7 @@ function getConsentMetadata() { return { gdpr: gdprDataHandler.getConsentMeta(), usp: uspDataHandler.getConsentMeta(), + gpp: gppDataHandler.getConsentMeta(), coppa: !!(config.getConfig('coppa')) } } @@ -486,86 +488,99 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); logMessage('Calling renderAd with adId :' + id); - if (doc && id) { - try { - // lookup ad by ad Id - const bid = auctionManager.findBidByAdId(id); - - if (bid) { - let shouldRender = true; - if (bid && bid.status === CONSTANTS.BID_STATUS.RENDERED) { - logWarn(`Ad id ${bid.adId} has been rendered before`); - events.emit(STALE_RENDER, bid); - if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { - shouldRender = false; - } - } - - if (shouldRender) { - // replace macros according to openRTB with price paid = bid.cpm - bid.ad = replaceAuctionPrice(bid.ad, bid.originalCpm || bid.cpm); - bid.adUrl = replaceAuctionPrice(bid.adUrl, bid.originalCpm || bid.cpm); - // replacing clickthrough if submitted - if (options && options.clickThrough) { - const {clickThrough} = options; - bid.ad = replaceClickThrough(bid.ad, clickThrough); - bid.adUrl = replaceClickThrough(bid.adUrl, clickThrough); - } - - // save winning bids - auctionManager.addWinningBid(bid); - - // emit 'bid won' event here - events.emit(BID_WON, bid); - - const {height, width, ad, mediaType, adUrl, renderer} = bid; - - const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); - insertElement(creativeComment, doc, 'html'); - - if (isRendererRequired(renderer)) { - executeRenderer(renderer, bid, doc); - reinjectNodeIfRemoved(creativeComment, doc, 'html'); - emitAdRenderSucceeded({ doc, bid, id }); - } else if ((doc === document && !inIframe()) || mediaType === 'video') { - const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`; - emitAdRenderFail({reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id}); - } else if (ad) { - doc.write(ad); - doc.close(); - setRenderSize(doc, width, height); - reinjectNodeIfRemoved(creativeComment, doc, 'html'); - callBurl(bid); - emitAdRenderSucceeded({ doc, bid, id }); - } else if (adUrl) { - const iframe = createInvisibleIframe(); - iframe.height = height; - iframe.width = width; - iframe.style.display = 'inline'; - iframe.style.overflow = 'hidden'; - iframe.src = adUrl; - - insertElement(iframe, doc, 'body'); - setRenderSize(doc, width, height); - reinjectNodeIfRemoved(creativeComment, doc, 'html'); - callBurl(bid); - emitAdRenderSucceeded({ doc, bid, id }); - } else { - const message = `Error trying to write ad. No ad for bid response id: ${id}`; - emitAdRenderFail({reason: NO_AD, message, bid, id}); - } - } - } else { - const message = `Error trying to write ad. Cannot find ad by given id : ${id}`; - emitAdRenderFail({ reason: CANNOT_FIND_AD, message, id }); + if (!id) { + const message = `Error trying to write ad Id :${id} to the page. Missing adId`; + emitAdRenderFail({ reason: MISSING_DOC_OR_ADID, message, id }); + return; + } + + try { + // lookup ad by ad Id + const bid = auctionManager.findBidByAdId(id); + if (!bid) { + const message = `Error trying to write ad. Cannot find ad by given id : ${id}`; + emitAdRenderFail({ reason: CANNOT_FIND_AD, message, id }); + return; + } + + if (bid.status === CONSTANTS.BID_STATUS.RENDERED) { + logWarn(`Ad id ${bid.adId} has been rendered before`); + events.emit(STALE_RENDER, bid); + if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { + return; } - } catch (e) { - const message = `Error trying to write ad Id :${id} to the page:${e.message}`; - emitAdRenderFail({ reason: EXCEPTION, message, id }); } - } else { - const message = `Error trying to write ad Id :${id} to the page. Missing document or adId`; - emitAdRenderFail({ reason: MISSING_DOC_OR_ADID, message, id }); + + // replace macros according to openRTB with price paid = bid.cpm + bid.ad = replaceAuctionPrice(bid.ad, bid.originalCpm || bid.cpm); + bid.adUrl = replaceAuctionPrice(bid.adUrl, bid.originalCpm || bid.cpm); + // replacing clickthrough if submitted + if (options && options.clickThrough) { + const {clickThrough} = options; + bid.ad = replaceClickThrough(bid.ad, clickThrough); + bid.adUrl = replaceClickThrough(bid.adUrl, clickThrough); + } + + // save winning bids + auctionManager.addWinningBid(bid); + + // emit 'bid won' event here + events.emit(BID_WON, bid); + + const {height, width, ad, mediaType, adUrl, renderer} = bid; + + // video module + const adUnitCode = bid.adUnitCode; + const adUnit = $$PREBID_GLOBAL$$.adUnits.filter(adUnit => adUnit.code === adUnitCode); + const videoModule = $$PREBID_GLOBAL$$.videoModule; + if (adUnit.video && videoModule) { + videoModule.renderBid(adUnit.video.divId, bid); + return; + } + + if (!doc) { + const message = `Error trying to write ad Id :${id} to the page. Missing document`; + emitAdRenderFail({ reason: MISSING_DOC_OR_ADID, message, id }); + return; + } + + const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); + insertElement(creativeComment, doc, 'html'); + + if (isRendererRequired(renderer)) { + executeRenderer(renderer, bid, doc); + reinjectNodeIfRemoved(creativeComment, doc, 'html'); + emitAdRenderSucceeded({ doc, bid, id }); + } else if ((doc === document && !inIframe()) || mediaType === 'video') { + const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`; + emitAdRenderFail({reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id}); + } else if (ad) { + doc.write(ad); + doc.close(); + setRenderSize(doc, width, height); + reinjectNodeIfRemoved(creativeComment, doc, 'html'); + callBurl(bid); + emitAdRenderSucceeded({ doc, bid, id }); + } else if (adUrl) { + const iframe = createInvisibleIframe(); + iframe.height = height; + iframe.width = width; + iframe.style.display = 'inline'; + iframe.style.overflow = 'hidden'; + iframe.src = adUrl; + + insertElement(iframe, doc, 'body'); + setRenderSize(doc, width, height); + reinjectNodeIfRemoved(creativeComment, doc, 'html'); + callBurl(bid); + emitAdRenderSucceeded({ doc, bid, id }); + } else { + const message = `Error trying to write ad. No ad for bid response id: ${id}`; + emitAdRenderFail({reason: NO_AD, message, bid, id}); + } + } catch (e) { + const message = `Error trying to write ad Id :${id} to the page:${e.message}`; + emitAdRenderFail({ reason: EXCEPTION, message, id }); } }); @@ -610,40 +625,58 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @alias module:pbjs.requestBids */ $$PREBID_GLOBAL$$.requestBids = (function() { - const delegate = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ortb2, metrics } = {}) { + const delegate = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ttlBuffer, ortb2, metrics, defer } = {}) { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + if (adUnitCodes && adUnitCodes.length) { + // if specific adUnitCodes supplied filter adUnits for those codes + adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); + } else { + // otherwise derive adUnitCodes from adUnits + adUnitCodes = adUnits && adUnits.map(unit => unit.code); + } const ortb2Fragments = { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) } - return startAuction({bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ortb2Fragments, metrics}); + return enrichFPD(GreedyPromise.resolve(ortb2Fragments.global)).then(global => { + ortb2Fragments.global = global; + return startAuction({bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ttlBuffer, ortb2Fragments, metrics, defer}); + }) }, 'requestBids'); return wrapHook(delegate, function requestBids(req = {}) { - // if the request does not specify adUnits, clone the global adUnit array - before - // any hook has a chance to run. + // unlike the main body of `delegate`, this runs before any other hook has a chance to; + // it's also not restricted in its return value in the way `async` hooks are. + + // if the request does not specify adUnits, clone the global adUnit array; // otherwise, if the caller goes on to use addAdUnits/removeAdUnits, any asynchronous logic // in any hook might see their effects. - req.metrics = newMetrics(); - req.metrics.checkpoint('requestBids'); let adUnits = req.adUnits || $$PREBID_GLOBAL$$.adUnits; req.adUnits = (isArray(adUnits) ? adUnits.slice() : [adUnits]); - return delegate.call(this, req); + + req.metrics = newMetrics(); + req.metrics.checkpoint('requestBids'); + req.defer = defer({promiseFactory: (r) => new Promise(r)}) + delegate.call(this, req); + return req.defer.promise; }); })(); -export const startAuction = hook('async', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ortb2Fragments, metrics } = {}) { +export const startAuction = hook('async', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, ttlBuffer, adUnitCodes, labels, auctionId, ortb2Fragments, metrics, defer } = {}) { const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); adUnits = useMetrics(metrics).measureTime('requestBids.validate', () => checkAdUnitSetup(adUnits)); - if (adUnitCodes && adUnitCodes.length) { - // if specific adUnitCodes supplied filter adUnits for those codes - adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); - } else { - // otherwise derive adUnitCodes from adUnits - adUnitCodes = adUnits && adUnits.map(unit => unit.code); + function auctionDone(bids, timedOut, auctionId) { + if (typeof bidsBackHandler === 'function') { + try { + bidsBackHandler(bids, timedOut, auctionId); + } catch (e) { + logError('Error executing bidsBackHandler', null, e); + } + } + defer.resolve({bids, timedOut, auctionId}) } /* @@ -664,6 +697,9 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: const tid = adUnit.ortb2Imp?.ext?.tid || generateUUID(); adUnit.transactionId = tid; + if (ttlBuffer != null && !adUnit.hasOwnProperty('ttlBuffer')) { + adUnit.ttlBuffer = ttlBuffer; + } // Populate ortb2Imp.ext.tid with transactionId. Specifying a transaction ID per item in the ortb impression array, lets multiple transaction IDs be transmitted in a single bid request. deepSetValue(adUnit, 'ortb2Imp.ext.tid', tid); @@ -688,35 +724,27 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: if (!adUnits || adUnits.length === 0) { logMessage('No adUnits configured. No bids requested.'); - if (typeof bidsBackHandler === 'function') { - // executeCallback, this will only be called in case of first request - try { - bidsBackHandler(); - } catch (e) { - logError('Error executing bidsBackHandler', null, e); - } - } - return; - } + auctionDone(); + } else { + const auction = auctionManager.createAuction({ + adUnits, + adUnitCodes, + callback: auctionDone, + cbTimeout, + labels, + auctionId, + ortb2Fragments, + metrics, + }); - const auction = auctionManager.createAuction({ - adUnits, - adUnitCodes, - callback: bidsBackHandler, - cbTimeout, - labels, - auctionId, - ortb2Fragments, - metrics, - }); + let adUnitsLen = adUnits.length; + if (adUnitsLen > 15) { + logInfo(`Current auction ${auction.getAuctionId()} contains ${adUnitsLen} adUnits.`, adUnits); + } - let adUnitsLen = adUnits.length; - if (adUnitsLen > 15) { - logInfo(`Current auction ${auction.getAuctionId()} contains ${adUnitsLen} adUnits.`, adUnits); + adUnitCodes.forEach(code => targeting.setLatestAuctionForAdUnit(code, auction.getAuctionId())); + auction.callBids(); } - - adUnitCodes.forEach(code => targeting.setLatestAuctionForAdUnit(code, auction.getAuctionId())); - auction.callBids(); }, 'startAuction'); export function executeCallbacks(fn, reqBidsConfigObj) { @@ -887,6 +915,14 @@ $$PREBID_GLOBAL$$.aliasBidder = function (bidderCode, alias, options) { } }; +/** + * @alias module:pbjs.aliasRegistry + */ +$$PREBID_GLOBAL$$.aliasRegistry = adapterManager.aliasRegistry; +config.getConfig('aliasRegistry', config => { + if (config.aliasRegistry === 'private') delete $$PREBID_GLOBAL$$.aliasRegistry; +}); + /** * The bid response object returned by an external bidder adapter during the auction. * @typedef {Object} AdapterBidResponse diff --git a/src/refererDetection.js b/src/refererDetection.js index 15c080f5c69..e0cb15522cc 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -120,6 +120,7 @@ export function detectReferer(win) { const stack = []; const ancestors = getAncestorOrigins(win); const maxNestedIframes = config.getConfig('maxNestedIframes'); + let currentWindow; let bestLocation; let bestCanonicalUrl; @@ -226,7 +227,11 @@ export function detectReferer(win) { const location = reachedTop || hasTopLocation ? bestLocation : null; const canonicalUrl = config.getConfig('pageUrl') || bestCanonicalUrl || null; - const page = ensureProtocol(canonicalUrl, win) || location; + let page = config.getConfig('pageUrl') || location || ensureProtocol(canonicalUrl, win); + + if (location && location.indexOf('?') > -1 && page.indexOf('?') === -1) { + page = `${page}${location.substring(location.indexOf('?'))}`; + } return { reachedTop, diff --git a/src/storageManager.js b/src/storageManager.js index 4ab224f8d9b..eaf35603c60 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,6 +1,7 @@ import {hook} from './hook.js'; import {hasDeviceAccess, checkCookieSupport, logError, logInfo, isPlainObject} from './utils.js'; import {bidderSettings as defaultBidderSettings} from './bidderSettings.js'; +import {VENDORLESS_GVLID} from './consentHandler.js'; const moduleTypeWhiteList = ['core', 'prebid-module']; @@ -33,7 +34,9 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = return storageAllowed == null ? false : storageAllowed; } - const isVendorless = moduleTypeWhiteList.includes(moduleType); + if (moduleTypeWhiteList.includes(moduleType)) { + gvlid = gvlid || VENDORLESS_GVLID; + } function isValid(cb) { if (!isBidderAllowed()) { @@ -45,7 +48,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = let hookDetails = { hasEnforcementHook: false } - validateStorageEnforcement(isVendorless, gvlid, bidderCode || moduleName, hookDetails, function(result) { + validateStorageEnforcement(gvlid, bidderCode || moduleName, hookDetails, function(result) { if (result && result.hasEnforcementHook) { value = cb(result); } else { @@ -296,7 +299,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = /** * This hook validates the storage enforcement if gdprEnforcement module is included */ -export const validateStorageEnforcement = hook('async', function(isVendorless, gvlid, moduleName, hookDetails, callback) { +export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, hookDetails, callback) { callback(hookDetails); }, 'validateStorageEnforcement'); diff --git a/src/targeting.js b/src/targeting.js index d2b3e9f8470..a75c9a2b52f 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,21 +1,42 @@ import { - uniques, isGptPubadsDefined, getHighestCpm, getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, timestamp, - deepAccess, deepClone, logError, logWarn, logInfo, isFn, isArray, logMessage, isStr, + deepAccess, + deepClone, + getHighestCpm, + getOldestHighestCpmBid, + groupBy, + isAdUnitCodeMatchingSlot, + isArray, + isFn, + isGptPubadsDefined, + isStr, + logError, + logInfo, + logMessage, + logWarn, + timestamp, + uniques, } from './utils.js'; -import { config } from './config.js'; -import { NATIVE_TARGETING_KEYS } from './native.js'; -import { auctionManager } from './auctionManager.js'; -import { sizeSupported } from './sizeMapping.js'; -import { ADPOD } from './mediaTypes.js'; -import { hook } from './hook.js'; -import { bidderSettings } from './bidderSettings.js'; -import {includes, find} from './polyfill.js'; +import {config} from './config.js'; +import {NATIVE_TARGETING_KEYS} from './native.js'; +import {auctionManager} from './auctionManager.js'; +import {ADPOD} from './mediaTypes.js'; +import {hook} from './hook.js'; +import {bidderSettings} from './bidderSettings.js'; +import {find, includes} from './polyfill.js'; import CONSTANTS from './constants.json'; var pbTargetingKeys = []; const MAX_DFP_KEYLENGTH = 20; -const TTL_BUFFER = 1000; +let DEFAULT_TTL_BUFFER = 1; + +config.getConfig('ttlBuffer', (cfg) => { + if (typeof cfg.ttlBuffer === 'number') { + DEFAULT_TTL_BUFFER = cfg.ttlBuffer; + } else { + logError('Invalid value for ttlBuffer', cfg.ttlBuffer); + } +}) const CFG_ALLOW_TARGETING_KEYS = `targetingControls.allowTargetingKeys`; const CFG_ADD_TARGETING_KEYS = `targetingControls.addTargetingKeys`; @@ -26,16 +47,23 @@ export const TARGETING_KEYS = Object.keys(CONSTANTS.TARGETING_KEYS).map( ); // return unexpired bids -const isBidNotExpired = (bid) => (bid.responseTimestamp + bid.ttl * 1000 - TTL_BUFFER) > timestamp(); +const isBidNotExpired = (bid) => (bid.responseTimestamp + (bid.ttl - (bid.hasOwnProperty('ttlBuffer') ? bid.ttlBuffer : DEFAULT_TTL_BUFFER)) * 1000) > timestamp(); // return bids whose status is not set. Winning bids can only have a status of `rendered`. const isUnusedBid = (bid) => bid && ((bid.status && !includes([CONSTANTS.BID_STATUS.RENDERED], bid.status)) || !bid.status); export let filters = { + isActualBid(bid) { + return bid.getStatusCode() === CONSTANTS.STATUS.GOOD + }, isBidNotExpired, isUnusedBid }; +export function isBidUsable(bid) { + return !Object.values(filters).some((predicate) => !predicate(bid)); +} + // If two bids are found for same adUnitCode, we will use the highest one to take part in auction // This can happen in case of concurrent auctions // If adUnitBidLimit is set above 0 return top N number of bids @@ -449,10 +477,7 @@ export function newTargeting(auctionManager) { bidsReceived = bidsReceived .filter(bid => deepAccess(bid, 'video.context') !== ADPOD) - .filter(bid => bid.mediaType !== 'banner' || sizeSupported([bid.width, bid.height])) - .filter(filters.isUnusedBid) - .filter(filters.isBidNotExpired) - ; + .filter(isBidUsable); return getHighestCpmBidsFromBidPool(bidsReceived, getOldestHighestCpmBid); } diff --git a/src/utils.js b/src/utils.js index 9e49e1687ae..8df45bab9d2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -710,7 +710,7 @@ export function getKeyByValue(obj, value) { export function getBidderCodes(adUnits = $$PREBID_GLOBAL$$.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) - .reduce(flatten, [])).reduce(flatten, []).filter(uniques); + .reduce(flatten, [])).reduce(flatten, []).filter((bidder) => typeof bidder !== 'undefined').filter(uniques); } export function isGptPubadsDefined() { @@ -1377,6 +1377,27 @@ export function safeJSONParse(data) { } catch (e) {} } +/** + * Returns a memoized version of `fn`. + * + * @param fn + * @param key cache key generator, invoked with the same arguments passed to `fn`. + * By default, the first argument is used as key. + * @return {function(): any} + */ +export function memoize(fn, key = function (arg) { return arg; }) { + const cache = new Map(); + const memoized = function () { + const cacheKey = key.apply(this, arguments); + if (!cache.has(cacheKey)) { + cache.set(cacheKey, fn.apply(this, arguments)); + } + return cache.get(cacheKey); + } + memoized.clear = cache.clear.bind(cache); + return memoized; +} + /** * Sets dataset attributes on a script * @param {Script} script diff --git a/src/utils/cpm.js b/src/utils/cpm.js new file mode 100644 index 00000000000..07113e7c944 --- /dev/null +++ b/src/utils/cpm.js @@ -0,0 +1,17 @@ +import {auctionManager} from '../auctionManager.js'; +import {bidderSettings} from '../bidderSettings.js'; +import {logError} from '../utils.js'; + +export function adjustCpm(cpm, bidResponse, bidRequest, {index = auctionManager.index, bs = bidderSettings} = {}) { + bidRequest = bidRequest || index.getBidRequest(bidResponse); + const bidCpmAdjustment = bs.get(bidResponse?.bidderCode || bidRequest?.bidder, 'bidCpmAdjustment'); + + if (bidCpmAdjustment && typeof bidCpmAdjustment === 'function') { + try { + return bidCpmAdjustment(cpm, Object.assign({}, bidResponse), bidRequest); + } catch (e) { + logError('Error during bid adjustment', e); + } + } + return cpm; +} diff --git a/src/utils/currency.js b/src/utils/currency.js new file mode 100644 index 00000000000..ab7e5faa1ea --- /dev/null +++ b/src/utils/currency.js @@ -0,0 +1,16 @@ +import {getGlobal} from '../prebidGlobal.js'; + +/** + * "best effort" wrapper around currency conversion; always returns an amount that may or may not be correct. + */ +export function beConvertCurrency(amount, from, to) { + if (from === to) return amount; + let result = amount; + if (typeof getGlobal().convertCurrency === 'function') { + try { + result = getGlobal().convertCurrency(amount, from, to); + } catch (e) { + } + } + return result; +} diff --git a/src/utils/promise.js b/src/utils/promise.js index 97c64a96f8b..69e40791ab1 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -90,6 +90,14 @@ export class GreedyPromise extends (getGlobal().Promise || Promise) { res.#parent = this; return res; } + + static resolve(value) { + return new this(resolve => resolve(value)) + } + + static reject(error) { + return new this((resolve, reject) => reject(error)) + } } /** diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index b0fbd7da806..b66d885c113 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1,5 +1,6 @@ // jscs:disable import CONSTANTS from 'src/constants.json'; +import {createBid} from '../../src/bidfactory.js'; const utils = require('src/utils.js'); function convertTargetingsFromOldToNew(targetings) { @@ -1268,7 +1269,7 @@ export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, ad if (typeof status !== 'undefined') { bid.status = status; } - return bid; + return Object.assign(createBid(CONSTANTS.STATUS.GOOD), bid); } export function getServerTestingsAds() { diff --git a/test/helpers/analytics.js b/test/helpers/analytics.js new file mode 100644 index 00000000000..b376118dc6f --- /dev/null +++ b/test/helpers/analytics.js @@ -0,0 +1,34 @@ +import * as pbEvents from 'src/events.js'; +import constants from '../../src/constants.json'; + +export function fireEvents(events = [ + constants.EVENTS.AUCTION_INIT, + constants.EVENTS.AUCTION_END, + constants.EVENTS.BID_REQUESTED, + constants.EVENTS.BID_RESPONSE, + constants.EVENTS.BID_WON +]) { + return events.map((ev, i) => { + ev = Array.isArray(ev) ? ev : [ev, {i: i}]; + pbEvents.emit.apply(null, ev) + return ev; + }); +} + +export function expectEvents(events) { + events = fireEvents(events); + return { + to: { + beTrackedBy(trackFn) { + events.forEach(([eventType, args]) => { + sinon.assert.calledWithMatch(trackFn, sinon.match({eventType, args})); + }); + }, + beBundledTo(bundleFn) { + events.forEach(([eventType, args]) => { + sinon.assert.calledWithMatch(bundleFn, sinon.match.any, eventType, sinon.match(args)) + }); + }, + }, + }; +} diff --git a/test/helpers/fpd.js b/test/helpers/fpd.js new file mode 100644 index 00000000000..89755f26541 --- /dev/null +++ b/test/helpers/fpd.js @@ -0,0 +1,71 @@ +import {dep, enrichFPD} from 'src/fpd/enrichment.js'; +import {GreedyPromise} from '../../src/utils/promise.js'; +import {deepClone} from '../../src/utils.js'; +import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; + +export function mockFpdEnrichments(sandbox, overrides = {}) { + overrides = Object.assign({}, { + // override window getters, required for ChromeHeadless, apparently it sees window.self !== window + getWindowTop() { + return window + }, + getWindowSelf() { + return window + }, + getHighEntropySUA() { + return GreedyPromise.resolve() + } + }, overrides) + Object.entries(overrides) + .filter(([k]) => dep[k]) + .forEach(([k, v]) => { + sandbox.stub(dep, k).callsFake(v); + }); + Object.entries({ + gdprConsent: gdprDataHandler, + uspConsent: uspDataHandler, + }).forEach(([ovKey, handler]) => { + const v = overrides[ovKey]; + if (v) { + sandbox.stub(handler, 'getConsentData').callsFake(v); + } + }) +} + +export function addFPDEnrichments(ortb2 = {}, overrides) { + const sandbox = sinon.sandbox.create(); + mockFpdEnrichments(sandbox, overrides) + return enrichFPD(GreedyPromise.resolve(deepClone(ortb2))).finally(() => sandbox.restore()); +} + +export const syncAddFPDEnrichments = synchronize(addFPDEnrichments); + +export function addFPDToBidderRequest(bidderRequest, overrides) { + overrides = Object.assign({}, { + getRefererInfo() { + return bidderRequest.refererInfo || {}; + }, + gdprConsent() { + return bidderRequest.gdprConsent; + }, + uspConsent() { + return bidderRequest.uspConsent; + } + }, overrides); + return addFPDEnrichments(bidderRequest.ortb2 || {}, overrides).then(ortb2 => { + return { + ...bidderRequest, + ortb2 + } + }); +} + +export const syncAddFPDToBidderRequest = synchronize(addFPDToBidderRequest); + +function synchronize(fn) { + return function () { + let result; + fn.apply(this, arguments).then(res => { result = res }); + return result; + } +} diff --git a/test/helpers/hookSetup.js b/test/helpers/hookSetup.js new file mode 100644 index 00000000000..2de35bb1dd4 --- /dev/null +++ b/test/helpers/hookSetup.js @@ -0,0 +1,5 @@ +import {hook} from '../../src/hook.js'; + +before(() => { + hook.ready(); +}); diff --git a/test/helpers/refererDetectionHelper.js b/test/helpers/refererDetectionHelper.js new file mode 100644 index 00000000000..bdbfb205cdb --- /dev/null +++ b/test/helpers/refererDetectionHelper.js @@ -0,0 +1,88 @@ +/** + * Build a walkable linked list of window-like objects for testing. + * + * @param {Array} urls Array of URL strings starting from the top window. + * @param {string} [topReferrer] + * @param {string} [canonicalUrl] + * @param {boolean} [ancestorOrigins] + * @returns {Object} + */ +export function buildWindowTree(urls, topReferrer = null, canonicalUrl = null, ancestorOrigins = false) { + /** + * Find the origin from a given fully-qualified URL. + * + * @param {string} url The fully qualified URL + * @returns {string|null} + */ + function getOrigin(url) { + const originRegex = new RegExp('^(https?://[^/]+/?)'); + + const result = originRegex.exec(url); + + if (result && result[0]) { + return result[0]; + } + + return null; + } + + const inaccessibles = []; + + let previousWindow, topWindow; + const topOrigin = getOrigin(urls[0]); + + const windowList = urls.map((url, index) => { + const thisOrigin = getOrigin(url), + sameOriginAsPrevious = index === 0 ? true : (getOrigin(urls[index - 1]) === thisOrigin), + sameOriginAsTop = thisOrigin === topOrigin; + + const win = { + location: { + href: url, + }, + document: { + referrer: index === 0 ? topReferrer : urls[index - 1] + } + }; + + if (topWindow == null) { + topWindow = win; + win.document.querySelector = function (selector) { + if (selector === 'link[rel=\'canonical\']') { + return { + href: canonicalUrl + }; + } + return null; + }; + } + + if (sameOriginAsPrevious) { + win.parent = previousWindow; + } else { + win.parent = inaccessibles[inaccessibles.length - 1]; + } + if (ancestorOrigins) { + win.location.ancestorOrigins = urls.slice(0, index).reverse().map(getOrigin); + } + win.top = sameOriginAsTop ? topWindow : inaccessibles[0]; + + const inWin = {parent: inaccessibles[inaccessibles.length - 1], top: inaccessibles[0]}; + if (index === 0) { + inWin.top = inWin; + } + ['document', 'location'].forEach((prop) => { + Object.defineProperty(inWin, prop, { + get: function () { + throw new Error('cross-origin access'); + } + }); + }); + inaccessibles.push(inWin); + previousWindow = win; + + return win; + }); + + return windowList[windowList.length - 1]; +} diff --git a/test/mocks/analyticsStub.js b/test/mocks/analyticsStub.js index 8507c5a6275..98e0f56688f 100644 --- a/test/mocks/analyticsStub.js +++ b/test/mocks/analyticsStub.js @@ -1,8 +1,10 @@ -import {_internal} from '../../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import {_internal, setDebounceDelay} from '../../libraries/analyticsAdapter/AnalyticsAdapter.js'; before(() => { // stub out analytics networking to avoid random events polluting the global xhr mock disableAjaxForAnalytics(); + // make analytics event handling synchronous + setDebounceDelay(0); }) export function disableAjaxForAnalytics() { diff --git a/test/mocks/timers.js b/test/mocks/timers.js new file mode 100644 index 00000000000..6efd798c881 --- /dev/null +++ b/test/mocks/timers.js @@ -0,0 +1,84 @@ +/* + * Provides wrappers for timers to allow easy cancelling and/or awaiting of outstanding timers. + * This helps avoid functionality leaking from one test to the next. + */ + +let wrappersActive = false; + +export function configureTimerInterceptors(debugLog = function() {}, generateStackTraces = false) { + if (wrappersActive) throw new Error(`Timer wrappers are already in place.`); + wrappersActive = true; + let theseWrappersActive = true; + + let originalSetTimeout = setTimeout, originalSetInterval = setInterval, originalClearTimeout = clearTimeout, originalClearInterval = clearInterval; + + let timerId = -1; + let timers = []; + + const waitOnTimersResolves = []; + function checkWaits() { + if (timers.length === 0) waitOnTimersResolves.forEach((r) => r()); + } + const waitAllActiveTimers = () => timers.length === 0 ? Promise.resolve() : new Promise((resolve) => waitOnTimersResolves.push(resolve)); + const clearAllActiveTimers = () => timers.forEach((timer) => timer.type === 'timeout' ? clearTimeout(timer.handle) : clearInterval(timer.handle)); + + const generateInterceptor = (type, originalFunctionWrapper) => (fn, delay, ...args) => { + timerId++; + debugLog(`Setting wrapped timeout ${timerId} for ${delay ?? 0}`); + const info = { timerId, type }; + if (generateStackTraces) { + try { + throw new Error(); + } catch (ex) { + info.stack = ex.stack; + } + } + info.handle = originalFunctionWrapper(info, fn, delay, ...args); + timers.push(info); + return info.handle; + }; + const setTimeoutInterceptor = generateInterceptor('timeout', (info, fn, delay, ...args) => originalSetTimeout(() => { + try { + debugLog(`Running timeout ${info.timerId}`); + fn(...args); + } finally { + const infoIndex = timers.indexOf(info); + if (infoIndex > -1) timers.splice(infoIndex, 1); + checkWaits(); + } + }, delay)); + + const setIntervalInterceptor = generateInterceptor('interval', (info, fn, interval, ...args) => originalSetInterval(() => { + debugLog(`Running interval ${info.timerId}`); + fn(...args); + }, interval)); + + const generateClearInterceptor = (type, originalClearFunction) => (handle) => { + originalClearFunction(handle); + const infoIndex = timers.findIndex((i) => i.handle === handle && i.type === type); + if (infoIndex > -1) timers.splice(infoIndex, 1); + checkWaits(); + } + const clearTimeoutInterceptor = generateClearInterceptor('timeout', originalClearTimeout); + const clearIntervalInterceptor = generateClearInterceptor('interval', originalClearInterval); + + setTimeout = setTimeoutInterceptor; + setInterval = setIntervalInterceptor; + clearTimeout = clearTimeoutInterceptor; + clearInterval = clearIntervalInterceptor; + + return { + waitAllActiveTimers, + clearAllActiveTimers, + timers, + restore: () => { + if (theseWrappersActive) { + theseWrappersActive = false; + setTimeout = originalSetTimeout; + setInterval = originalSetInterval; + clearTimeout = originalClearTimeout; + clearInterval = originalClearInterval; + } + } + } +} diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 06ab8838985..62c00e04403 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -1,18 +1,17 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; -import { server } from 'test/mocks/xhr.js'; +import {server} from 'test/mocks/xhr.js'; import {disableAjaxForAnalytics, enableAjaxForAnalytics} from '../mocks/analyticsStub.js'; +import {clearEvents} from 'src/events.js'; +import { + DEFAULT_EXCLUDE_EVENTS, + DEFAULT_INCLUDE_EVENTS, + setDebounceDelay +} from '../../libraries/analyticsAdapter/AnalyticsAdapter.js'; -const REQUEST_BIDS = CONSTANTS.EVENTS.REQUEST_BIDS; -const BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; -const BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; const BID_WON = CONSTANTS.EVENTS.BID_WON; -const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -const AD_RENDER_FAILED = CONSTANTS.EVENTS.AD_RENDER_FAILED; -const AD_RENDER_SUCCEEDED = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED; -const AUCTION_DEBUG = CONSTANTS.EVENTS.AUCTION_DEBUG; -const ADD_AD_UNITS = CONSTANTS.EVENTS.ADD_AD_UNITS; +const NO_BID = CONSTANTS.EVENTS.NO_BID; const AnalyticsAdapter = require('libraries/analyticsAdapter/AnalyticsAdapter.js').default; const config = { @@ -35,6 +34,7 @@ FEATURE: Analytics Adapters API afterEach(function () { adapter.disableAnalytics(); + clearEvents(); }); it('should track enable status in `enabled`', () => { @@ -43,21 +43,21 @@ FEATURE: Analytics Adapters API expect(adapter.enabled).to.equal(true); adapter.disableAnalytics(); expect(adapter.enabled).to.equal(false); - }) + }); it(`SHOULD call the endpoint WHEN an event occurs that is to be tracked`, function () { - const eventType = BID_REQUESTED; - const args = { some: 'data' }; + const eventType = BID_WON; + const args = {some: 'data'}; - adapter.track({ eventType, args }); + adapter.track({eventType, args}); let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {some: 'data'}, eventType: 'bidRequested'}); + expect(result).to.deep.equal({args: {some: 'data'}, eventType}); }); it(`SHOULD queue the event first and then track it WHEN an event occurs before tracking library is available`, function () { - const eventType = BID_RESPONSE; - const args = { wat: 'wot' }; + const eventType = BID_WON; + const args = {wat: 'wot'}; events.emit(eventType, args); adapter.enableAnalytics(); @@ -65,9 +65,58 @@ FEATURE: Analytics Adapters API // As now AUCTION_DEBUG is triggered for WARNINGS too, the BID_RESPONSE goes last in the array const index = server.requests.length - 1; let result = JSON.parse(server.requests[index].requestBody); - expect(result).to.deep.equal({eventType: 'bidResponse', args: {wat: 'wot'}}); + expect(result).to.deep.equal({eventType, args: {wat: 'wot'}}); }); + describe('event filters', () => { + function fireEvents() { + events.emit(BID_WON, {}); + events.emit(NO_BID, {}); + } + function getEvents(ev) { + return server.requests + .map(r => JSON.parse(r.requestBody)) + .filter(r => r.eventType === ev) + } + + Object.entries({ + 'whitelist includeEvents': { + includeEvents: [BID_WON] + }, + 'blacklist excludeEvents': { + excludeEvents: [NO_BID] + }, + 'give precedence to exclude over include': { + includeEvents: [BID_WON, NO_BID], + excludeEvents: [NO_BID] + } + }).forEach(([t, config]) => { + it(`should ${t}`, () => { + fireEvents(); + adapter.enableAnalytics(config); + expect(getEvents(BID_WON).length).to.eql(1); + expect(getEvents(NO_BID).length).to.eql(0); + fireEvents(); + expect(getEvents(BID_WON).length).to.eql(2); + expect(getEvents(NO_BID).length).to.eql(0); + }) + }) + }) + + it('should prevent infinite loops when track triggers other events', () => { + let i = 0; + adapter.track = ((orig) => { + return function (event) { + i++; + orig.call(this, event); + events.emit(BID_WON, {}) + } + })(adapter.track); + adapter.enableAnalytics(config); + events.emit(BID_WON, {}); + expect(i >= 100).to.eql(false); + }) + describe(`WHEN an event occurs after enable analytics\n`, function () { beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); // these tests shouldn't be affected by previous tests @@ -77,108 +126,26 @@ FEATURE: Analytics Adapters API events.getEvents.restore(); }); - it('SHOULD call global when a bidWon event occurs', function () { - const eventType = BID_WON; - const args = { more: 'info' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {more: 'info'}, eventType: 'bidWon'}); - }); - - it('SHOULD call global when a adRenderFailed event occurs', function () { - const eventType = AD_RENDER_FAILED; - const args = { call: 'adRenderFailed' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'adRenderFailed'}, eventType: 'adRenderFailed'}); - }); - - it('SHOULD call global when a adRenderSucceeded event occurs', function () { - const eventType = AD_RENDER_SUCCEEDED; - const args = { call: 'adRenderSucceeded' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'adRenderSucceeded'}, eventType: 'adRenderSucceeded'}); - }); - - it('SHOULD call global when an auction debug event occurs', function () { - const eventType = AUCTION_DEBUG; - const args = { call: 'auctionDebug' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'auctionDebug'}, eventType: 'auctionDebug'}); - }); - - it('SHOULD call global when an addAdUnits event occurs', function () { - const eventType = ADD_AD_UNITS; - const args = { call: 'addAdUnits' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'addAdUnits'}, eventType: 'addAdUnits'}); - }); - - it('SHOULD call global when a requestBids event occurs', function () { - const eventType = REQUEST_BIDS; - const args = { call: 'request' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'request'}, eventType: 'requestBids'}); - }); - - it('SHOULD call global when a bidRequest event occurs', function () { - const eventType = BID_REQUESTED; - const args = { call: 'request' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'request'}, eventType: 'bidRequested'}); - }); - - it('SHOULD call global when a bidResponse event occurs', function () { - const eventType = BID_RESPONSE; - const args = { call: 'response' }; - - adapter.enableAnalytics(); - events.emit(eventType, args); - - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'response'}, eventType: 'bidResponse'}); - }); - - it('SHOULD call global when a bidTimeout event occurs', function () { - const eventType = BID_TIMEOUT; - const args = { call: 'timeout' }; + Object.values(DEFAULT_INCLUDE_EVENTS).forEach(eventType => { + it(`SHOULD call global when a ${eventType} event occurs`, () => { + const args = {more: 'info'}; - adapter.enableAnalytics(); - events.emit(eventType, args); + adapter.enableAnalytics(); + events.emit(eventType, args); - let result = JSON.parse(server.requests[0].requestBody); - expect(result).to.deep.equal({args: {call: 'timeout'}, eventType: 'bidTimeout'}); + let result = JSON.parse(server.requests[server.requests.length - 1].requestBody); + sinon.assert.match(result, { + eventType, + args: { + more: 'info' + }, + }); + }); }); it('SHOULD NOT call global again when adapter.enableAnalytics is called with previous timeout', function () { - const eventType = BID_TIMEOUT; - const args = { call: 'timeout' }; + const eventType = BID_WON; + const args = {call: 'timeout'}; events.emit(eventType, args); adapter.enableAnalytics(); @@ -189,7 +156,7 @@ FEATURE: Analytics Adapters API describe(`AND sampling is enabled\n`, function () { const eventType = BID_WON; - const args = { more: 'info' }; + const args = {more: 'info'}; beforeEach(function () { sinon.stub(Math, 'random').returns(0.5); @@ -225,3 +192,39 @@ FEATURE: Analytics Adapters API }); }); }); + +describe('Analytics asynchronous event tracking', () => { + before(() => { + setDebounceDelay(100); + }); + after(() => { + setDebounceDelay(0); + }); + + let adapter, clock; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + adapter = new AnalyticsAdapter(config); + adapter.track = sinon.stub(); + adapter.enableAnalytics({}); + }); + + afterEach(() => { + clock.restore(); + }) + + it('does not call track as long as events are coming', () => { + events.emit(BID_WON, {i: 0}); + sinon.assert.notCalled(adapter.track); + clock.tick(10); + events.emit(BID_WON, {i: 1}); + sinon.assert.notCalled(adapter.track); + clock.tick(10); + sinon.assert.notCalled(adapter.track); + clock.tick(100); + sinon.assert.calledTwice(adapter.track); + sinon.assert.calledWith(adapter.track.firstCall, {eventType: BID_WON, args: {i: 0}}); + sinon.assert.calledWith(adapter.track.secondCall, {eventType: BID_WON, args: {i: 1}}); + }); +}) diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 49ae13c43cc..e1ecf801aa3 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -21,6 +21,8 @@ import {auctionManager} from '../../src/auctionManager.js'; import 'modules/debugging/index.js' // some tests look for debugging side effects import {AuctionIndex} from '../../src/auctionIndex.js'; import {expect} from 'chai'; +import {deepClone} from '../../src/utils.js'; +import { IMAGE as ortbNativeRequest } from 'src/native.js'; var assert = require('assert'); @@ -57,7 +59,16 @@ function mockBid(opts) { 'currency': 'USD', 'netRevenue': true, 'ttl': 360, - getSize: () => '300x250' + getSize: () => '300x250', + getIdentifiers() { + return { + src: this.source, + bidder: this.bidderCode, + bidId: this.requestId, + transactionId: this.transactionId, + auctionId: this.auctionId + } + } }; } @@ -175,7 +186,8 @@ describe('auctionmanager.js', function () { source: 'client', mediaType: 'banner', meta: { - advertiserDomains: ['adomain'] + advertiserDomains: ['adomain'], + primaryCatId: 'IAB-test' } }; @@ -189,6 +201,7 @@ describe('auctionmanager.js', function () { expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source; expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType; expected[ CONSTANTS.TARGETING_KEYS.ADOMAIN ] = bid.meta.advertiserDomains[0]; + expected[ CONSTANTS.TARGETING_KEYS.ACAT ] = bid.meta.primaryCatId; if (bid.mediaType === 'video') { expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; @@ -279,6 +292,12 @@ describe('auctionmanager.js', function () { val: function (bidResponse) { return bidResponse.meta.advertiserDomains[0]; } + }, + { + key: CONSTANTS.TARGETING_KEYS.ACAT, + val: function (bidResponse) { + return bidResponse.meta.primaryCatId; + } } ] @@ -356,6 +375,12 @@ describe('auctionmanager.js', function () { val: function (bidResponse) { return bidResponse.meta.advertiserDomains[0]; } + }, + { + key: CONSTANTS.TARGETING_KEYS.ACAT, + val: function (bidResponse) { + return bidResponse.meta.primaryCatId; + } } ] @@ -444,6 +469,24 @@ describe('auctionmanager.js', function () { assert.deepEqual(response, expected); }); + it('Should set targeting as expecting when pbs is enabled', function () { + config.setConfig({ + s2sConfig: { + accountId: '1', + enabled: true, + defaultVendor: 'appnexus', + bidders: ['appnexus'], + timeout: 1000, + adapter: 'prebidServer' + } + }); + + $$PREBID_GLOBAL$$.bidderSettings = {}; + let expected = getDefaultExpected(bid); + let response = getKeyValueTargetingPairs(bid.bidderCode, bid); + assert.deepEqual(response, expected); + }); + it('Custom bidCpmAdjustment for one bidder and inherit standard but doesn\'t use standard bidCpmAdjustment', function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -726,6 +769,7 @@ describe('auctionmanager.js', function () { let auction; let ajaxStub; let bids; + let bidderRequests; let makeRequestsStub; before(function () { @@ -749,7 +793,8 @@ describe('auctionmanager.js', function () { adUnitCodes = [ADUNIT_CODE]; auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 3000}); bids = TEST_BIDS.slice(); - makeRequestsStub.returns(bids.map(b => mockBidRequest(b, {auctionId: auction.getAuctionId()}))); + bidderRequests = bids.map(b => mockBidRequest(b, {auctionId: auction.getAuctionId()})); + makeRequestsStub.returns(bidderRequests); indexAuctions = [auction]; createAuctionStub = sinon.stub(auctionModule, 'newAuction'); createAuctionStub.returns(auction); @@ -803,25 +848,32 @@ describe('auctionmanager.js', function () { assert.equal(registeredBid.adserverTargeting.extra, 'stuff'); }); - it('installs publisher-defined renderers on bids', function () { - let renderer = { - url: 'renderer.js', - render: (bid) => bid - }; - Object.assign(adUnits[0], {renderer}); - - let bids1 = Object.assign({}, - bids[0], - { - bidderCode: BIDDER_CODE, - mediaType: 'video-outstream', - } - ); - spec.interpretResponse.returns(bids1); - auction.callBids(); - const addedBid = auction.getBidsReceived().pop(); - assert.equal(addedBid.renderer.url, 'renderer.js'); - }); + describe('install publisher-defined renderers', () => { + Object.entries({ + 'on adUnit': () => adUnits[0], + 'on bid': () => bidderRequests[0].bids[0], + }).forEach(([t, getObj]) => { + it(t, () => { + let renderer = { + url: 'renderer.js', + render: (bid) => bid + }; + + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: BIDDER_CODE, + mediaType: 'video-outstream', + } + ); + Object.assign(getObj(), {renderer}); + spec.interpretResponse.returns(bids1); + auction.callBids(); + const addedBid = auction.getBidsReceived().pop(); + assert.equal(addedBid.renderer.url, 'renderer.js'); + }) + }) + }) it('installs publisher-defined backup renderers on bids', function () { let renderer = { @@ -936,6 +988,12 @@ describe('auctionmanager.js', function () { const addedBid = find(auction.getBidsReceived(), bid => bid.adUnitCode == ADUNIT_CODE); assert.equal(addedBid.renderer.url, 'renderer.js'); }); + + it('sets bidResponse.ttlBuffer from adUnit.ttlBuffer', () => { + adUnits[0].ttlBuffer = 0; + auction.callBids(); + expect(auction.getBidsReceived()[0].ttlBuffer).to.eql(0); + }); }); describe('when auction timeout is 20', function () { @@ -1251,6 +1309,81 @@ describe('auctionmanager.js', function () { }); }); + if (FEATURES.NATIVE) { + describe('addBidResponse native', function () { + let makeRequestsStub; + let ajaxStub; + let spec; + let auction; + + beforeEach(function () { + makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); + + const adUnits = [{ + code: ADUNIT_CODE, + transactionId: ADUNIT_CODE, + bids: [ + {bidder: BIDDER_CODE, params: {placementId: 'id'}}, + ], + nativeOrtbRequest: ortbNativeRequest.ortb, + mediaTypes: { native: { type: 'image' } } + }]; + auction = auctionModule.newAuction({adUnits, adUnitCodes: [ADUNIT_CODE], callback: function() {}, cbTimeout: 3000}); + indexAuctions = [auction]; + const createAuctionStub = sinon.stub(auctionModule, 'newAuction'); + createAuctionStub.returns(auction); + + spec = mockBidder(BIDDER_CODE); + registerBidder(spec); + }); + + afterEach(function () { + ajaxStub.restore(); + auctionModule.newAuction.restore(); + adapterManager.makeBidRequests.restore(); + }); + + it('should add legacy fields to native response', function () { + let nativeBid = mockBid(); + nativeBid.mediaType = 'native'; + nativeBid.native = { + ortb: { + ver: '1.2', + assets: [ + { id: 2, title: { text: 'Sample title' } }, + { id: 4, data: { value: 'Sample body' } }, + { id: 3, data: { value: 'Sample sponsoredBy' } }, + { id: 1, img: { url: 'https://www.example.com/image.png', w: 200, h: 200 } }, + { id: 5, img: { url: 'https://www.example.com/icon.png', w: 32, h: 32 } } + ], + link: { url: 'http://www.click.com' }, + eventtrackers: [ + { event: 1, method: 1, url: 'http://www.imptracker.com' }, + { event: 1, method: 2, url: 'http://www.jstracker.com/file.js' } + ] + } + } + + let bidRequest = mockBidRequest(nativeBid, { mediaType: { native: ortbNativeRequest } }); + makeRequestsStub.returns([bidRequest]); + + spec.interpretResponse.returns(nativeBid); + auction.callBids(); + + const addedBid = auction.getBidsReceived().pop(); + assert.equal(addedBid.native.body, 'Sample body') + assert.equal(addedBid.native.title, 'Sample title') + assert.equal(addedBid.native.sponsoredBy, 'Sample sponsoredBy') + assert.equal(addedBid.native.clickUrl, 'http://www.click.com') + assert.equal(addedBid.native.image, 'https://www.example.com/image.png') + assert.equal(addedBid.native.icon, 'https://www.example.com/icon.png') + assert.equal(addedBid.native.impressionTrackers[0], 'http://www.imptracker.com') + assert.equal(addedBid.native.javascriptTrackers, '') + }); + }); + } + describe('getMediaTypeGranularity', function () { it('video', function () { let mediaTypes = { video: {id: '1'} }; @@ -1323,6 +1456,7 @@ describe('auctionmanager.js', function () { getAdUnits: () => getBidRequests().flatMap(br => br.bids).map(br => ({ code: br.adUnitCode, transactionId: br.transactionId, mediaTypes: br.mediaTypes })), getAuctionId: () => '1', addBidReceived: () => true, + addBidRejected: () => true, getTimeout: () => 1000, getAuctionStart: () => start, } @@ -1382,26 +1516,32 @@ describe('auctionmanager.js', function () { bidRequests = null; }); - it('should call auction done after bid is added to auction for mediaType banner', function () { - let ADUNIT_CODE2 = 'adUnitCode2'; - let BIDDER_CODE2 = 'sampleBidder2'; - - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1, transactionId: ADUNIT_CODE1 })]; - let bids2 = [mockBid({ bidderCode: BIDDER_CODE2, transactionId: ADUNIT_CODE2 })]; - bidRequests = [ - mockBidRequest(bids[0]), - mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), - mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE2 }) - ]; - let cbs = auctionCallbacks(doneSpy, auction); - cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bids[0]); - cbs.adapterDone.call(bidRequests[0]); - cbs.addBidResponse.call(bidRequests[1], ADUNIT_CODE1, bids1[0]); - cbs.adapterDone.call(bidRequests[1]); - cbs.addBidResponse.call(bidRequests[2], ADUNIT_CODE2, bids2[0]); - cbs.adapterDone.call(bidRequests[2]); - assert.equal(doneSpy.callCount, 1); - }); + Object.entries({ + 'added to': (cbs) => cbs.addBidResponse, + 'rejected from': (cbs) => cbs.addBidResponse.reject, + }).forEach(([t, getMethod]) => { + it(`should call auction done after bid is ${t} auction for mediaType banner`, function () { + let ADUNIT_CODE2 = 'adUnitCode2'; + let BIDDER_CODE2 = 'sampleBidder2'; + + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1, transactionId: ADUNIT_CODE1 })]; + let bids2 = [mockBid({ bidderCode: BIDDER_CODE2, transactionId: ADUNIT_CODE2 })]; + bidRequests = [ + mockBidRequest(bids[0]), + mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), + mockBidRequest(bids2[0], { adUnitCode: ADUNIT_CODE2 }) + ]; + let cbs = auctionCallbacks(doneSpy, auction); + const method = getMethod(cbs); + method(ADUNIT_CODE, bids[0]); + cbs.adapterDone.call(bidRequests[0]); + method(ADUNIT_CODE1, bids1[0]); + cbs.adapterDone.call(bidRequests[1]); + method(ADUNIT_CODE2, bids2[0]); + cbs.adapterDone.call(bidRequests[2]); + assert.equal(doneSpy.callCount, 1); + }); + }) it('should call auction done after prebid cache is complete for mediaType video', function() { bids[0].mediaType = 'video'; @@ -1432,6 +1572,15 @@ describe('auctionmanager.js', function () { assert.equal(doneSpy.callCount, 1); }); + it('should convert cpm to number', () => { + auction.addBidReceived = sinon.spy(); + const cbs = auctionCallbacks(doneSpy, auction); + const bid = {...bids[0], cpm: '1.23'} + bidRequests = [mockBidRequest(bid)]; + cbs.addBidResponse.call(bidRequests[0], ADUNIT_CODE, bid); + sinon.assert.calledWith(auction.addBidReceived, sinon.match({cpm: 1.23})); + }) + describe('when addBidResponse hook returns promises', () => { let resolvers, callbacks, bids; @@ -1543,6 +1692,87 @@ describe('auctionmanager.js', function () { }); }) }); + + describe('when bids are rejected', () => { + let cbs, bid, expectedRejection; + const onBidRejected = sinon.stub(); + const REJECTION_REASON = 'Bid rejected'; + const AU_CODE = 'au'; + + function rejectHook(fn, adUnitCode, bid, reject) { + reject(REJECTION_REASON); + reject(REJECTION_REASON); // second call should do nothing + } + + before(() => { + addBidResponse.before(rejectHook, 999); + events.on(CONSTANTS.EVENTS.BID_REJECTED, onBidRejected); + }); + + after(() => { + addBidResponse.getHooks({hook: rejectHook}).remove(); + events.off(CONSTANTS.EVENTS.BID_REJECTED, onBidRejected); + }); + + beforeEach(() => { + onBidRejected.reset(); + bid = mockBid({bidderCode: BIDDER_CODE}); + bidRequests = [ + mockBidRequest(bid), + ]; + cbs = auctionCallbacks(doneSpy, auction); + expectedRejection = sinon.match(Object.assign({}, bid, { + cpm: parseFloat(bid.cpm), + rejectionReason: REJECTION_REASON, + adUnitCode: AU_CODE + })); + auction.addBidRejected = sinon.stub(); + }); + + Object.entries({ + 'with addBidResponse.reject': () => cbs.addBidResponse.reject(AU_CODE, deepClone(bid), REJECTION_REASON), + 'from addBidResponse hooks': () => cbs.addBidResponse(AU_CODE, deepClone(bid)) + }).forEach(([t, rejectBid]) => { + describe(t, () => { + it('should emit a BID_REJECTED event', () => { + rejectBid(); + sinon.assert.calledWith(onBidRejected, expectedRejection); + }); + + it('should pass bid to auction.addBidRejected', () => { + rejectBid(); + sinon.assert.calledWith(auction.addBidRejected, expectedRejection); + }); + }) + }); + + it('should return a NO_BID replacement', () => { + const noBid = cbs.addBidResponse.reject(AU_CODE, {...bid, statusMessage: 'Bid available', status: CONSTANTS.BID_STATUS.RENDERED}, 'Rejected'); + sinon.assert.match(noBid, { + status: CONSTANTS.BID_STATUS.BID_REJECTED, + statusMessage: 'Bid returned empty or error response', + cpm: 0, + requestId: bid.requestId, + auctionId: bid.auctionId, + adUnitCode: AU_CODE, + rejectionReason: undefined, + }); + }); + + it('should return NO_BID replacement when rejected bid is not a "proper" bid', () => { + const noBid = cbs.addBidResponse.reject(AU_CODE, {}); + sinon.assert.match(noBid, { + status: CONSTANTS.BID_STATUS.BID_REJECTED, + statusMessage: 'Bid returned empty or error response', + cpm: 0, + }); + }) + + it('addBidResponse hooks should not be able to reject the same bid twice', () => { + cbs.addBidResponse(AU_CODE, bid); + expect(auction.addBidRejected.calledOnce).to.be.true; + }); + }) }); describe('auctionOptions', function() { diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js new file mode 100644 index 00000000000..3363e1867d3 --- /dev/null +++ b/test/spec/fpd/enrichment_spec.js @@ -0,0 +1,213 @@ +import {dep, enrichFPD} from '../../../src/fpd/enrichment.js'; +import {hook} from '../../../src/hook.js'; +import {expect} from 'chai/index.mjs'; +import {config} from 'src/config.js'; + +describe('FPD enrichment', () => { + let sandbox; + before(() => { + hook.ready(); + }); + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + config.resetConfig(); + }); + + function fpd(ortb2 = {}) { + return enrichFPD(Promise.resolve(ortb2)); + } + + function mockWindow() { + return { + innerHeight: 1, + innerWidth: 1, + navigator: { + language: '' + }, + document: { + querySelector: sinon.stub() + } + }; + } + + function testWindows(getWindow, fn) { + Object.entries({ + 'top': ['getWindowTop', 'getWindowSelf'], + 'self': ['getWindowSelf', 'getWindowTop'] + }).forEach(([t, [winWorks, winThrows]]) => { + describe(`using window.${t}`, () => { + beforeEach(() => { + sandbox.stub(dep, winWorks).callsFake(getWindow); + sandbox.stub(dep, winThrows).throws(new Error()); + }); + fn(); + }); + }); + } + + describe('site', () => { + it('sets page, ref, domain, and publisher.domain', () => { + const refererInfo = { + page: 'www.example.com', + ref: 'referrer.com' + }; + sandbox.stub(dep, 'getRefererInfo').callsFake(() => refererInfo); + sandbox.stub(dep, 'findRootDomain').callsFake((dom) => `publisher.${dom}`); + return fpd().then(ortb2 => { + sinon.assert.match(ortb2.site, { + page: 'www.example.com', + domain: 'example.com', + ref: 'referrer.com', + publisher: { + domain: 'publisher.example.com' + } + }); + }); + }); + + describe('keywords', () => { + let metaTag; + beforeEach(() => { + metaTag = document.createElement('meta'); + metaTag.name = 'keywords'; + metaTag.content = 'kw1, kw2'; + document.head.appendChild(metaTag); + }); + afterEach(() => { + document.head.removeChild(metaTag); + }); + + testWindows(() => window, () => { + it(`sets kewwords from meta tag`, () => { + return fpd().then(ortb2 => { + expect(ortb2.site.keywords).to.eql('kw1,kw2'); + }); + }); + }); + }); + + it('should not set keywords if meta tag is not present', () => { + return fpd().then(ortb2 => { + expect(ortb2.site.hasOwnProperty('keywords')).to.be.false; + }); + }); + + it('respects pub-provided fpd', () => { + return fpd({ + site: { + publisher: { + domain: 'pub.com' + } + } + }).then(ortb2 => { + expect(ortb2.site.publisher.domain).to.eql('pub.com'); + }); + }); + }); + + describe('device', () => { + let win; + beforeEach(() => { + win = mockWindow(); + }); + testWindows(() => win, () => { + it('sets w/h', () => { + win.innerHeight = 123; + win.innerWidth = 321; + return fpd().then(ortb2 => { + sinon.assert.match(ortb2.device, { + w: 321, + h: 123, + }); + }); + }); + + it('sets ua', () => { + win.navigator.userAgent = 'mock-ua'; + return fpd().then(ortb2 => { + expect(ortb2.device.ua).to.eql('mock-ua'); + }) + }); + + it('sets language', () => { + win.navigator.language = 'lang-ignored'; + return fpd().then(ortb2 => { + expect(ortb2.device.language).to.eql('lang'); + }) + }); + }); + }); + + describe('regs', () => { + describe('gpc', () => { + let win; + beforeEach(() => { + win = mockWindow(); + }); + testWindows(() => win, () => { + it('is set if globalPrivacyControl is set', () => { + win.navigator.globalPrivacyControl = true; + return fpd().then(ortb2 => { + expect(ortb2.regs.ext.gpc).to.eql(1); + }); + }); + + it('is not set otherwise', () => { + return fpd().then(ortb2 => { + expect(ortb2.regs?.ext?.gpc).to.not.exist; + }) + }) + }); + }) + describe('coppa', () => { + [[true, 1], [false, 0]].forEach(([cfgVal, regVal]) => { + it(`is set to ${regVal} if config = ${cfgVal}`, () => { + config.setConfig({coppa: cfgVal}); + return fpd().then(ortb2 => { + expect(ortb2.regs.coppa).to.eql(regVal); + }) + }); + }) + + it('is not set if not configured', () => { + return fpd().then(ortb2 => { + expect(ortb2.regs?.coppa).to.not.exist; + }) + }) + }); + }); + + describe('sua', () => { + it('does not set device.sua if resolved sua is null', () => { + sandbox.stub(dep, 'getHighEntropySUA').returns(Promise.resolve()) + return fpd().then(ortb2 => { + expect(ortb2.device.sua).to.not.exist; + }) + }); + it('uses low entropy values if uaHints is []', () => { + sandbox.stub(dep, 'getLowEntropySUA').callsFake(() => ({mock: 'sua'})); + config.setConfig({ + firstPartyData: { + uaHints: [], + } + }) + return fpd().then(ortb2 => { + expect(ortb2.device.sua).to.eql({mock: 'sua'}); + }) + }); + it('uses high entropy values otherwise', () => { + sandbox.stub(dep, 'getHighEntropySUA').callsFake((hints) => Promise.resolve({hints})); + config.setConfig({ + firstPartyData: { + uaHints: ['h1', 'h2'] + } + }); + return fpd().then(ortb2 => { + expect(ortb2.device.sua).to.eql({hints: ['h1', 'h2']}) + }) + }); + }); +}); diff --git a/test/spec/fpd/gdpr_spec.js b/test/spec/fpd/gdpr_spec.js new file mode 100644 index 00000000000..8fc04815112 --- /dev/null +++ b/test/spec/fpd/gdpr_spec.js @@ -0,0 +1,47 @@ +import {gdprDataHandler} from '../../../src/adapterManager.js'; +import {enrichFPDHook} from '../../../modules/consentManagement.js'; + +describe('GDPR FPD enrichment', () => { + let sandbox, consent; + beforeEach(() => { + consent = null; + sandbox = sinon.sandbox.create(); + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => consent); + }); + afterEach(() => { + sandbox.restore(); + }) + + function callHook() { + let result; + enrichFPDHook((res) => { result = res }, Promise.resolve({})); + return result; + } + + it('sets gdpr properties from gdprDataHandler', () => { + consent = {gdprApplies: true, consentString: 'consent'}; + return callHook().then(ortb2 => { + expect(ortb2.regs.ext.gdpr).to.eql(1); + expect(ortb2.user.ext.consent).to.eql('consent'); + }) + }); + + it('does not set it if missing', () => { + return callHook().then((ortb2) => { + expect(ortb2).to.eql({}); + }) + }); + + it('sets user.ext.consent, but not regs.ext.gdpr, if gpdrApplies is not a boolean', () => { + consent = {consentString: 'mock-consent'}; + return callHook().then(ortb2 => { + expect(ortb2).to.eql({ + user: { + ext: { + consent: 'mock-consent' + } + } + }) + }) + }); +}); diff --git a/test/spec/fpd/rootDomain_spec.js b/test/spec/fpd/rootDomain_spec.js new file mode 100644 index 00000000000..008ef749edc --- /dev/null +++ b/test/spec/fpd/rootDomain_spec.js @@ -0,0 +1,61 @@ +import {expect} from 'chai/index.js'; +import {findRootDomain, coreStorage} from 'src/fpd/rootDomain.js'; + +describe('findRootDomain', function () { + let sandbox, cookies, rejectDomain; + + beforeEach(function () { + findRootDomain.clear(); + cookies = {}; + rejectDomain = ''; + sandbox = sinon.createSandbox(); + sandbox.stub(coreStorage, 'cookiesAreEnabled').returns(true); + sandbox.stub(coreStorage, 'setCookie').callsFake((cookie, value, expiration, sameSite, domain) => { + if (rejectDomain !== domain) { + if (new Date(expiration) <= Date.now()) { + delete cookies[cookie]; + } else { + cookies[cookie] = value; + } + } + }) + sandbox.stub(coreStorage, 'getCookie').callsFake((cookie) => { + return cookies[cookie] + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + + after(() => { + findRootDomain.clear(); + }) + + it('should just find the root domain', function () { + rejectDomain = 'co.uk'; + var domain = findRootDomain('sub.realdomain.co.uk'); + expect(domain).to.be.eq('realdomain.co.uk'); + expect(cookies).to.eql({}); + }); + + it('should find the full domain when no subdomain is present', function () { + rejectDomain = 'co.uk'; + var domain = findRootDomain('realdomain.co.uk'); + expect(domain).to.be.eq('realdomain.co.uk'); + expect(cookies).to.eql({}); + }); + + it('should return domain as-is if cookies are disabled', () => { + coreStorage.cookiesAreEnabled.returns(false); + expect(findRootDomain('sub.example.com')).to.eql('sub.example.com'); + sinon.assert.notCalled(coreStorage.setCookie); + }); + + it('should memoize default value', () => { + const domain = findRootDomain(); + expect(domain.length > 0).to.be.true; + expect(findRootDomain()).to.eql(domain); + sinon.assert.calledOnce(coreStorage.getCookie); + }); +}); diff --git a/test/spec/fpd/usp_spec.js b/test/spec/fpd/usp_spec.js new file mode 100644 index 00000000000..f616b086ffa --- /dev/null +++ b/test/spec/fpd/usp_spec.js @@ -0,0 +1,36 @@ +import {enrichFPDHook} from '../../../modules/consentManagementUsp.js'; +import {uspDataHandler} from '../../../src/adapterManager.js'; + +describe('FPD enrichment USP', () => { + let sandbox, consent; + beforeEach(() => { + consent = null; + sandbox = sinon.sandbox.create(); + sandbox.stub(uspDataHandler, 'getConsentData').callsFake(() => consent); + }); + + afterEach(() => { + sandbox.restore(); + }); + + function callHook() { + let result; + enrichFPDHook((res) => { + result = res; + }, Promise.resolve({})); + return result; + } + + it('sets regs.ext.us_privacy from uspDataHandler', () => { + consent = '1NN'; + return callHook().then(ortb2 => { + expect(ortb2.regs.ext.us_privacy).to.eql('1NN'); + }) + }); + + it('does not set if missing', () => { + return callHook().then(ortb2 => { + expect(ortb2).to.eql({}); + }) + }); +}); diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js index dae0e313387..496c005f470 100644 --- a/test/spec/modules/1plusXRtdProvider_spec.js +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -9,6 +9,8 @@ import { extractConsent, getPapiUrl } from 'modules/1plusXRtdProvider'; +import assert from 'assert'; +import { extractFpid } from '../../../modules/1plusXRtdProvider'; describe('1plusXRtdProvider', () => { // Fake server config @@ -276,41 +278,50 @@ describe('1plusXRtdProvider', () => { it('throws an error if the consent is malformed', () => { const consent1 = { - gdpr: { - gdprApplies: 1 - } - } - const consent2 = { gdpr: { consentString: 'myConsent' } } - const consent3 = { + const consent2 = { gdpr: { gdprApplies: 1, consentString: 3 } } - const consent4 = { + const consent3 = { gdpr: { gdprApplies: 'yes', consentString: 'myConsent' } } - const consent5 = { - gdprApplies: 1, - consentString: 'myConsent' + const consent4 = { + gdpr: {} } - for (const consent in [consent1, consent2, consent3, consent4, consent5]) { + for (const consent of [consent1, consent2, consent3, consent4]) { + var failed = false; try { extractConsent(consent) - assert(false, 'Should be throwing an exception') - } catch (e) { } + } catch (e) { + failed = true; + } finally { + assert(failed, 'Should be throwing an exception') + } } }) }) + describe('extractFpid', () => { + it('correctly extracts an ope fpid if present', () => { + window.localStorage.setItem('ope_fpid', 'oneplusx_test_key') + const id1 = extractFpid() + window.localStorage.removeItem('ope_fpid') + const id2 = extractFpid() + expect(id1).to.equal('oneplusx_test_key') + expect(id2).to.equal(null) + }) + }) + describe('getPapiUrl', () => { const customer = 'acme' const consent = { @@ -320,11 +331,16 @@ describe('1plusXRtdProvider', () => { } } - it('correctly builds URLs based on consent', () => { + it('correctly builds URLs if gdpr parameters are present', () => { const url1 = getPapiUrl(customer) const url2 = getPapiUrl(customer, extractConsent(consent)) expect(['&consent_string=myConsent&gdpr_applies=1', '&gdpr_applies=1&consent_string=myConsent']).to.contain(url2.replace(url1, '')) }) + + it('correctly builds URLs if fpid parameters are present') + const url1 = getPapiUrl(customer) + const url2 = getPapiUrl(customer, {}, 'my_first_party_id') + expect(url2.replace(url1, '')).to.equal('&fpid=my_first_party_id') }) describe('updateBidderConfig', () => { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 3657f7da912..3b3c05660df 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -43,6 +43,17 @@ describe('33acrossBidAdapter:', function () { ah: 500, mtp: 0 } + }, + sua: { + browsers: [{ + brand: 'Google Chrome', + version: ['104', '0', '5112', '79'] + }], + platform: { + brand: 'macOS', + version: ['11', '6', '8'] + }, + model: '' } }, id: 'r1', @@ -207,6 +218,14 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withReferer = referer => { + Object.assign(ttxRequest.site, { + ref: referer + }); + + return this; + }; + this.withSchain = schain => { Object.assign(ttxRequest, { source: { @@ -305,7 +324,23 @@ describe('33acrossBidAdapter:', function () { adUnitCode: 'div-id', auctionId: 'r1', mediaTypes: {}, - transactionId: 't1' + transactionId: 't1', + ortb2: { + device: { + sua: { + browsers: [{ + brand: 'Google Chrome', + version: ['104', '0', '5112', '79'] + }], + platform: { + brand: 'macOS', + version: ['11', '6', '8'] + }, + model: '', + mobile: false + } + } + } } ]; @@ -322,6 +357,22 @@ describe('33acrossBidAdapter:', function () { auctionId: 'r1', mediaTypes: {}, transactionId: 't2', + ortb2: { + device: { + sua: { + browsers: [{ + brand: 'Google Chrome', + version: ['104', '0', '5112', '79'] + }], + platform: { + brand: 'macOS', + version: ['11', '6', '8'] + }, + model: '', + mobile: false + } + } + }, ...bidParams }); @@ -1144,26 +1195,51 @@ describe('33acrossBidAdapter:', function () { }); }); - context('when referer value is available', function() { - it('returns corresponding server requests with site.page set', function() { - const bidderRequest = { - refererInfo: { - page: 'http://foo.com/bar' - } - }; + context('when refererInfo values are available', function() { + context('when refererInfo.page is defined', function() { + it('returns corresponding server requests with site.page set', function() { + const bidderRequest = { + refererInfo: { + page: 'http://foo.com/bar' + } + }; - const ttxRequest = new TtxRequestBuilder() - .withBanner() - .withProduct() - .withPageUrl('http://foo.com/bar') - .build(); - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withPageUrl('http://foo.com/bar') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + + context('when refererInfo.ref is defined', function() { + it('returns corresponding server requests with site.ref set', function() { + const bidderRequest = { + refererInfo: { + ref: 'google.com' + } + }; + + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withReferer('google.com') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); }); }); @@ -1203,7 +1279,7 @@ describe('33acrossBidAdapter:', function () { }); context('when referer value is not available', function() { - it('returns corresponding server requests without site.page set', function() { + it('returns corresponding server requests without site.page and site.ref set', function() { const bidderRequest = { refererInfo: {} }; diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 8198bd75381..765b320f925 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -43,7 +43,10 @@ describe('33acrossIdSystem', () => { expect(request.method).to.equal('GET'); expect(request.withCredentials).to.be.true; - expect(request.url).to.contain('https://lexicon.33across.com/v1/envelope?pid=12345'); + + const regExp = new RegExp('https://lexicon.33across.com/v1/envelope\\?pid=12345&gdpr=\\d&src=pbjs&ver=$prebid.version$'); + + expect(request.url).to.match(regExp); expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; }); diff --git a/test/spec/modules/ViouslyBidAdapter_spec.js b/test/spec/modules/ViouslyBidAdapter_spec.js new file mode 100644 index 00000000000..612c5f87138 --- /dev/null +++ b/test/spec/modules/ViouslyBidAdapter_spec.js @@ -0,0 +1,378 @@ +import {expect} from 'chai'; + +import { deepClone, mergeDeep } from 'src/utils'; +import { createEidsArray } from 'modules/userId/eids.js'; + +import {spec as adapter} from 'modules/viouslyBidAdapter'; + +import sinon from 'sinon'; +import { config } from 'src/config.js'; + +const CURRENCY = 'EUR'; +const TTL = 60; +const HTTP_METHOD = 'POST'; +const REQUEST_URL = 'https://bidder.viously.com/bid'; + +const VALID_BID_VIDEO = { + bidder: 'viously', + bidId: '5e6f7g8h', + adUnitCode: 'id-5678', + params: { + pid: '123e4567-e89b-12d3-a456-426614174001' + }, + mediaTypes: { + video: { + playerSize: [640, 360], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } + } +}; + +const VALID_REQUEST_VIDEO = { + method: HTTP_METHOD, + url: REQUEST_URL, + data: { + pid: '123e4567-e89b-12d3-a456-426614174001', + currency_code: CURRENCY, + placements: [ + { + id: 'id-5678', + bid_id: '5e6f7g8h', + video_params: { + context: 'instream', + playbackmethod: [1, 2, 3, 4], + size: ['640x360'] + } + } + ] + } +}; + +const VALID_GDPR = { + gdprApplies: true, + apiVersion: 2, + consentString: 'abcdefgh', + addtlConsent: '1~12345678', + vendorData: { + purpose: { + consents: { + 1: true + } + } + } +}; +const US_PRIVACY = '1YNN'; + +describe('ViouslyAdapter', function () { + describe('isBidRequestValid', function () { + describe('Check method return', function () { + it('should return true', function () { + expect(adapter.isBidRequestValid(VALID_BID_VIDEO)).to.equal(true); + }); + + it('should return false because the pid is missing', function () { + let wrongBid = deepClone(VALID_BID_VIDEO); + delete wrongBid.params.pid; + + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + }); + + it('should return false because the video context parameter is missing', function () { + let wrongBid = deepClone(VALID_BID_VIDEO); + + delete wrongBid.mediaTypes.video.context; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + }); + }); + }); + + describe('buildRequests', function () { + describe('Check method return', function () { + it('should return the right formatted video requests', function() { + expect(adapter.buildRequests([VALID_BID_VIDEO])).to.deep.equal(VALID_REQUEST_VIDEO); + }); + + it('should return the right formatted request with the referer info', function() { + let bidderRequest = { + refererInfo: { + page: 'https://www.example.com/test' + } + }; + + let requests = mergeDeep(deepClone(VALID_REQUEST_VIDEO), { + data: { + domain: 'www.example.com', + page_domain: 'https://www.example.com/test' + } + }); + + expect(adapter.buildRequests([VALID_BID_VIDEO], bidderRequest)).to.deep.equal(requests); + }); + + it('should return the right formatted request with the referer info from config', function() { + /** Mock the config.getConfig method */ + sinon.stub(config, 'getConfig') + .withArgs('pageUrl') + .returns('https://www.example.com/page'); + + let requests = mergeDeep(deepClone(VALID_REQUEST_VIDEO), { + data: { + domain: 'www.example.com', + page_domain: 'https://www.example.com/page' + } + }); + + expect(adapter.buildRequests([VALID_BID_VIDEO])).to.deep.equal(requests); + + config.getConfig.restore(); + }); + + it('should return the right formatted request with GDPR Consent info', function() { + let bidderRequest = { + gdprConsent: VALID_GDPR + }; + + let requests = mergeDeep(deepClone(VALID_REQUEST_VIDEO), { + data: { + gdpr: true, + gdpr_consent: 'abcdefgh', + addtl_consent: '1~12345678' + } + }); + + expect(adapter.buildRequests([VALID_BID_VIDEO], bidderRequest)).to.deep.equal(requests); + }); + + it('should return the right formatted request with US Privacy info', function() { + let bidderRequest = { + uspConsent: US_PRIVACY + }; + + let requests = mergeDeep(deepClone(VALID_REQUEST_VIDEO), { + data: { + us_privacy: US_PRIVACY + } + }); + + expect(adapter.buildRequests([VALID_BID_VIDEO], bidderRequest)).to.deep.equal(requests); + }); + + // TODO: Supply chain + it('should return the right formatted request with Supply Chain info', function() { + let schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'test1.com', + 'sid': '00001', + 'hp': 1 + }, + { + 'asi': 'test2-2.com', + 'sid': '00002', + 'hp': 2 + } + ] + }; + + let bid = mergeDeep(deepClone(VALID_BID_VIDEO), { + schain: schain + }); + + let requests = mergeDeep(deepClone(VALID_REQUEST_VIDEO), { + data: { + schain: schain + } + }); + + expect(adapter.buildRequests([bid])).to.deep.equal(requests); + }); + + it('should return the right formatted request with User Ids info', function() { + let userIds = { + idl_env: '1234-5678-9012-3456', // Liveramp + netId: 'testnetid123', // NetId + IDP: 'userIDP000', // IDP + fabrickId: 'fabrickId9000', // FabrickId + uid2: { id: 'testuid2' } // UID 2.0 + }; + + let bid = mergeDeep(deepClone(VALID_BID_VIDEO), { + userIds: userIds + }, { + userIdAsEids: createEidsArray(userIds) + }); + + let requests = mergeDeep(deepClone(VALID_REQUEST_VIDEO), { + data: { + users_uid: createEidsArray(userIds) + } + }); + + expect(adapter.buildRequests([bid])).to.deep.equal(requests); + }); + + it('should return the right formatted request with endpint test', function() { + let endpoint = 'https://bid-test.viously.com/prebid'; + + let bid = mergeDeep(deepClone(VALID_BID_VIDEO), { + params: { + endpoint: endpoint + } + }); + + let requests = mergeDeep(deepClone(VALID_REQUEST_VIDEO)); + + requests.url = endpoint; + + expect(adapter.buildRequests([bid])).to.deep.equal(requests); + }); + + // TODO: Floor + }); + }); + + describe('interpretResponse', function() { + describe('Check method return', function () { + it('should return the right formatted response', function() { + let response = { + body: { + ads: [ + { + bid: false, + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-0', + bid_id: '1234' + }, + { + bid: true, + creative_id: '2468', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-1', + bid_id: '5678', + cpm: 8, + ad: 'vast xml', + ad_url: 'http://www.example.com/vast', + type: 'video', + size: '640x480', + nurl: [ + 'win.domain.com' + ] + }, + { + bid: true, + creative_id: '1469', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', + bid_id: '2570', + cpm: 4, + ad: 'vast xml', + type: 'video', + size: '640x480', + } + ] + } + }; + let requests = { + data: { + placements: [ + { + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-0', + bid_id: '1234' + }, + { + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-1', + bid_id: '5678' + }, + { + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', + bid_id: '2570' + } + ] + } + }; + + let formattedReponse = [ + { + requestId: '5678', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-1', + cpm: 8, + width: '640', + height: '480', + creativeId: '2468', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'video', + meta: {}, + vastUrl: 'http://www.example.com/vast', + nurl: [ + 'win.domain.com' + ] + }, + { + requestId: '2570', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', + cpm: 4, + width: '640', + height: '480', + creativeId: '1469', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'video', + meta: {}, + vastXml: 'vast xml', + nurl: [] + } + ]; + + expect(adapter.interpretResponse(response, requests)).to.deep.equal(formattedReponse); + }); + }); + }); + + describe('onBidWon', function() { + describe('Check methods succeed', function () { + it('should not throw error', function() { + let bids = [ + { + requestId: '5678', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-1', + cpm: 8, + width: '640', + height: '480', + creativeId: '2468', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'video', + meta: {}, + vastUrl: 'http://www.example.com/vast', + nurl: [ + 'win.domain.com' + ] + }, + { + requestId: '2570', + id: 'id-0157324f-bee4-5390-a14c-47a7da3eb73c-3', + cpm: 4, + width: '640', + height: '480', + creativeId: '1469', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'video', + meta: {}, + vastXml: 'vast xml', + nurl: [] + } + ]; + + bids.forEach(function(bid) { + expect(adapter.onBidWon.bind(adapter, bid)).to.not.throw(); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/aaxBlockmeter_spec.js b/test/spec/modules/aaxBlockmeter_spec.js new file mode 100644 index 00000000000..f9704361976 --- /dev/null +++ b/test/spec/modules/aaxBlockmeter_spec.js @@ -0,0 +1,58 @@ +import {aaxBlockmeterRtdModule} from '../../../modules/aaxBlockmeterRtdProvider.js'; +import * as sinon from 'sinon'; +import {assert} from 'chai'; + +let sandbox; +let getTargetingDataSpy; + +const config = { + dataProviders: [{ + 'name': 'aaxBlockmeter', + 'params': { + 'pub': 'publisher_id', + } + }] +}; + +describe('aaxBlockmeter realtime module', function () { + beforeEach(function () { + sandbox = sinon.sandbox.create(); + window.aax = window.aax || {}; + window.aax.getTargetingData = getTargetingDataSpy = sandbox.spy(); + }); + + afterEach(function () { + sandbox.restore(); + window.aax = {}; + }); + + it('init should return false when config is empty', function () { + assert.equal(aaxBlockmeterRtdModule.init({}), false); + }); + + it('init should return false when config.params id is empty', function () { + assert.equal(aaxBlockmeterRtdModule.init({params: {}}), false); + }); + + it('init should return true when config.params.pub is not string', function () { + assert.equal(aaxBlockmeterRtdModule.init({params: {pub: 12345}}), false); + }); + + it('init should return true when config.params.pub id is passed and is string typed', function () { + assert.equal(aaxBlockmeterRtdModule.init(config.dataProviders[0]), true); + }); + + describe('getTargetingData should work correctly', function () { + it('should return ad unit codes when ad units are present', function () { + const codes = ['code1', 'code2']; + assert.deepEqual(aaxBlockmeterRtdModule.getTargetingData(codes), { + code1: {'atk': 'code1'}, + code2: {'atk': 'code2'}, + }); + }); + + it('should call aax.getTargetingData if loaded', function () { + aaxBlockmeterRtdModule.getTargetingData([], config.dataProviders[0], null); + }); + }); +}); diff --git a/test/spec/modules/adWMGAnalyticsAdapter_spec.js b/test/spec/modules/adWMGAnalyticsAdapter_spec.js index ab8336c7126..1e0da1bb3c8 100644 --- a/test/spec/modules/adWMGAnalyticsAdapter_spec.js +++ b/test/spec/modules/adWMGAnalyticsAdapter_spec.js @@ -1,6 +1,7 @@ import adWMGAnalyticsAdapter from 'modules/adWMGAnalyticsAdapter.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; +import {expectEvents} from '../../helpers/analytics.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); let constants = require('src/constants.json'); @@ -140,14 +141,15 @@ describe('adWMG Analytics', function () { } }); - events.emit(constants.EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); - events.emit(constants.EVENTS.NO_BID, {}); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_WON, wonRequest); - sinon.assert.callCount(adWMGAnalyticsAdapter.track, 7); + expectEvents([ + [constants.EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits}], + [constants.EVENTS.BID_REQUESTED, {}], + [constants.EVENTS.BID_RESPONSE, bidResponse], + [constants.EVENTS.NO_BID, {}], + [constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs], + [constants.EVENTS.AUCTION_END, {}], + [constants.EVENTS.BID_WON, wonRequest], + ]).to.beTrackedBy(adWMGAnalyticsAdapter.track); }); it('should be two xhr requests', function () { diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index aee85412104..581f3cb1b87 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -83,34 +83,27 @@ describe('adagio analytics adapter', () => { timeToRespond: 132, }; - // Step 1: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + const testEvents = { + [constants.EVENTS.BID_REQUESTED]: bidRequest, + [constants.EVENTS.BID_RESPONSE]: bidResponse, + [constants.EVENTS.AUCTION_END]: {} + }; - // Step 2: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + // Step 1-3: Send events + Object.entries(testEvents).forEach(([ev, payload]) => events.emit(ev, payload)); - // Step 3: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {}); + function eventItem(eventName, args) { + return sinon.match({ + action: 'pb-analytics-event', + ts: sinon.match((val) => val !== undefined), + data: { + eventName, + args + } + }) + } - sandbox.assert.callCount(adagioQueuePushSpy, 3); - - const call0 = adagioQueuePushSpy.getCall(0); - expect(call0.args[0].action).to.equal('pb-analytics-event'); - expect(call0.args[0].ts).to.not.be.undefined; - expect(call0.args[0].data).to.not.be.undefined; - expect(call0.args[0].data).to.deep.equal({eventName: constants.EVENTS.BID_REQUESTED, args: bidRequest}); - - const call1 = adagioQueuePushSpy.getCall(1); - expect(call1.args[0].action).to.equal('pb-analytics-event'); - expect(call1.args[0].ts).to.not.be.undefined; - expect(call1.args[0].data).to.not.be.undefined; - expect(call1.args[0].data).to.deep.equal({eventName: constants.EVENTS.BID_RESPONSE, args: bidResponse}); - - const call2 = adagioQueuePushSpy.getCall(2); - expect(call2.args[0].action).to.equal('pb-analytics-event'); - expect(call2.args[0].ts).to.not.be.undefined; - expect(call2.args[0].data).to.not.be.undefined; - expect(call2.args[0].data).to.deep.equal({eventName: constants.EVENTS.AUCTION_END, args: {}}); + Object.entries(testEvents).forEach(([ev, payload]) => sinon.assert.calledWith(adagioQueuePushSpy, eventItem(ev, payload))); }); }); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index e2fde8ff2e3..ba1db077488 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -149,10 +149,8 @@ describe('Adagio bid adapter', () => { site: { ext: { data: { - environment: 'desktop', pagetype: 'abc', - category: ['cat1', 'cat2', 'cat3'], - subcategory: [] + category: ['cat1', 'cat2', 'cat3'] } } } @@ -167,17 +165,11 @@ describe('Adagio bid adapter', () => { return utils.deepAccess(config, key); }); - setExtraParam(bid, 'environment'); - expect(bid.params.environment).to.equal('desktop'); - setExtraParam(bid, 'pagetype') expect(bid.params.pagetype).to.equal('article'); setExtraParam(bid, 'category'); expect(bid.params.category).to.equal('cat1'); // Only the first value is kept - - setExtraParam(bid, 'subcategory'); - expect(bid.params.subcategory).to.be.undefined; }); it('should use the adUnit param unit if defined', function() { @@ -389,8 +381,8 @@ describe('Adagio bid adapter', () => { context: 'outstream', playerSize: [[300, 250]], mimes: ['video/mp4'], - api: 5, // will be removed because invalid - playbackmethod: [7], // will be removed because invalid + api: 'val', // will be removed because invalid + playbackmethod: ['val'], // will be removed because invalid } }, }).withParams({ @@ -400,7 +392,7 @@ describe('Adagio bid adapter', () => { skipafter: 4, minduration: 10, maxduration: 30, - placement: [3], + placement: 3, protocols: [8] } }).build(); @@ -415,7 +407,7 @@ describe('Adagio bid adapter', () => { skipafter: 4, minduration: 10, maxduration: 30, - placement: [3], + placement: 3, protocols: [8], w: 300, h: 250 @@ -784,8 +776,6 @@ describe('Adagio bid adapter', () => { adUnitElementId: 'gpt-adunit-code', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', supportIObs: true }, adUnitCode: 'adunit-code', @@ -833,8 +823,6 @@ describe('Adagio bid adapter', () => { site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { @@ -868,8 +856,6 @@ describe('Adagio bid adapter', () => { site: 'SITE-NAME', pagetype: 'ARTICLE', category: 'NEWS', - subcategory: 'SPORT', - environment: 'desktop', aDomain: ['advertiser.com'], mediaType: 'banner', meta: { @@ -1392,19 +1378,6 @@ describe('Adagio bid adapter', () => { }); describe.skip('optional params auto detection', function() { - it('should auto detect environment', function() { - const getDeviceStub = sandbox.stub(_features, 'getDevice'); - - getDeviceStub.returns(5); - expect(adagio.autoDetectEnvironment()).to.eq('tablet'); - - getDeviceStub.returns(4); - expect(adagio.autoDetectEnvironment()).to.eq('mobile'); - - getDeviceStub.returns(2); - expect(adagio.autoDetectEnvironment()).to.eq('desktop'); - }); - it('should auto detect adUnitElementId when GPT is used', function() { sandbox.stub(utils, 'getGptSlotInfoForAdUnitCode').withArgs('banner').returns({divId: 'gpt-banner'}); expect(adagio.autoDetectAdUnitElementId('banner')).to.eq('gpt-banner'); diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js index 40bf354c4d9..4ea525c59d5 100644 --- a/test/spec/modules/adhashBidAdapter_spec.js +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -60,6 +60,12 @@ describe('adhashBidAdapter', function () { bid.params.platformURL = 'https://'; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when bidderURL is present but not https://', function () { + const bid = { ...validBid }; + bid.params.bidderURL = 'http://example.com/'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -73,11 +79,11 @@ describe('adhashBidAdapter', function () { it('should build the request correctly', function () { const result = spec.buildRequests( [ bidRequest ], - { gdprConsent: { gdprApplies: true, consentString: 'example' }, refererInfo: { referer: 'http://example.com/' } } + { gdprConsent: { gdprApplies: true, consentString: 'example' }, refererInfo: { topmostLocation: 'https://example.com/path.html' } } ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=3.2&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); @@ -93,7 +99,7 @@ describe('adhashBidAdapter', function () { const result = spec.buildRequests([ bidRequest ], { gdprConsent: { gdprApplies: true, consentString: 'example' } }); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=3.2&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); @@ -127,9 +133,15 @@ describe('adhashBidAdapter', function () { creatives: [{ costEUR: 1.234 }], advertiserDomains: 'adhash.com', badWords: [ - ['onqjbeq1', 'full', 1], - ['onqjbeq2', 'partial', 1], + ['onqjbeq', 'full', 1], + ['onqjbeqo', 'partial', 1], ['tbbqjbeq', 'full', -1], + ['fgnegf', 'starts', 1], + ['raqf', 'ends', 1], + ['kkk[no]lll', 'regexp', 1], + ['дума', 'full', 1], + ['старт', 'starts', 1], + ['край', 'ends', 1], ], maxScore: 2 } @@ -155,42 +167,84 @@ describe('adhashBidAdapter', function () { it('should return empty array when there are bad words (full)', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { - return 'example text badWord1 badWord1 example badWord1 text' + ' word'.repeat(493); + return 'example text badword badword example badword text' + ' word'.repeat(993); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + + it('should return empty array when there are bad words (full cyrillic)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'example text дума дума example дума text' + ' текст'.repeat(993); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (partial)', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { - return 'example text partialBadWord2 badword2 example BadWord2text' + ' word'.repeat(494); + return 'example text partialbadwordb badwordb example badwordbtext' + ' word'.repeat(994); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + + it('should return empty array when there are bad words (starts)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'example text startsWith starts text startsAgain' + ' word'.repeat(994); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + + it('should return empty array when there are bad words (starts cyrillic)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'example text стартТекст старт text стартТекст' + ' дума'.repeat(994); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + + it('should return empty array when there are bad words (ends)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'example text wordEnds ends text anotherends' + ' word'.repeat(994); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + + it('should return empty array when there are bad words (ends cyrillic)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'example text ДругКрай край text ощеединкрай' + ' дума'.repeat(994); + }); + expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); + }); + + it('should return empty array when there are bad words (regexp)', function () { + bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + return 'example text xxxayyy zzxxxAyyyzz text xxxbyyy' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return non-empty array when there are not enough bad words (full)', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { - return 'example text badWord1 badWord1 example text' + ' word'.repeat(494); + return 'example text badword badword example text' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are not enough bad words (partial)', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { - return 'example text partialBadWord2 example' + ' word'.repeat(496); + return 'example text partialbadwordb example' + ' word'.repeat(996); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are no-bad word matches', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { - return 'example text partialBadWord1 example text' + ' word'.repeat(495); + return 'example text partialbadword example text' + ' word'.repeat(995); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are bad words and good words', function () { bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { - return 'example text badWord1 badWord1 example badWord1 goodWord goodWord ' + ' word'.repeat(492); + return 'example text badword badword example badword goodWord goodWord ' + ' word'.repeat(992); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); diff --git a/test/spec/modules/adlooxRtdProvider_spec.js b/test/spec/modules/adlooxRtdProvider_spec.js index 236e053e58c..5b99789981f 100644 --- a/test/spec/modules/adlooxRtdProvider_spec.js +++ b/test/spec/modules/adlooxRtdProvider_spec.js @@ -1,5 +1,6 @@ import adapterManager from 'src/adapterManager.js'; import analyticsAdapter from 'modules/adlooxAnalyticsAdapter.js'; +import {auctionManager} from 'src/auctionManager.js'; import { config as _config } from 'src/config.js'; import { expect } from 'chai'; import * as events from 'src/events.js'; @@ -75,14 +76,6 @@ describe('Adloox RTD Provider', function () { done(); }); - it('should reject non-string config.params.api_origin', function (done) { - const ret = rtdProvider.init({ params: { api_origin: null } }); - - expect(ret).is.false; - - done(); - }); - it('should reject less than one config.params.imps', function (done) { const ret = rtdProvider.init({ params: { imps: 0 } }); @@ -147,31 +140,37 @@ describe('Adloox RTD Provider', function () { }); let server = null; - let __config = null, CONFIG = null; - let getConfigStub, setConfigStub; + let CONFIG = null; beforeEach(function () { server = sinon.createFakeServer(); - __config = {}; CONFIG = utils.deepClone(config); - getConfigStub = sinon.stub(_config, 'getConfig').callsFake(function (path) { - return utils.deepAccess(__config, path); - }); - setConfigStub = sinon.stub(_config, 'setConfig').callsFake(function (obj) { - utils.mergeDeep(__config, obj); - }); }); afterEach(function () { - setConfigStub.restore(); - getConfigStub.restore(); - getConfigStub = setConfigStub = undefined; CONFIG = null; - __config = null; server.restore(); server = null; }); it('should fetch segments', function (done) { - const req = {}; + const req = { + adUnitCodes: [ adUnit.code ], + ortb2Fragments: { + global: { + site: { + ext: { + data: { + } + } + }, + user: { + ext: { + data: { + } + } + } + } + } + }; const adUnitWithSegments = utils.deepClone(adUnit); const getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ adUnits: [ adUnitWithSegments ] @@ -201,119 +200,28 @@ describe('Adloox RTD Provider', function () { }); it('should set ad server targeting', function (done) { - utils.deepSetValue(__config, 'ortb2.site.ext.data.adloox_rtd.ok', true); - const adUnitWithSegments = utils.deepClone(adUnit); utils.deepSetValue(adUnitWithSegments, 'ortb2Imp.ext.data.adloox_rtd.dis', [ 50, 60 ]); const getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ adUnits: [ adUnitWithSegments ] }); - const targetingData = rtdProvider.getTargetingData([ adUnitWithSegments.code ], CONFIG, null, { - getFPD: () => ({ - global: __config.ortb2 - }) + const auction = { adUnits: [ adUnitWithSegments ] }; + const getAuctionStub = sinon.stub(auctionManager.index, 'getAuction').returns({ + adUnits: [ adUnitWithSegments ], + getFPD: () => { return { global: { site: { ext: { data: { adloox_rtd: { ok: true } } } } } } } }); + + const targetingData = rtdProvider.getTargetingData([ adUnitWithSegments.code ], CONFIG, null, auction); expect(Object.keys(targetingData).length).is.equal(1); expect(Object.keys(targetingData[adUnit.code]).length).is.equal(2); expect(targetingData[adUnit.code].adl_ok).is.equal(1); expect(targetingData[adUnit.code].adl_dis.length).is.equal(2); + getAuctionStub.restore(); getGlobalStub.restore(); done(); }); }); - - describe('measure atf', function () { - const adUnitCopy = utils.deepClone(adUnit); - - const ratio = 0.38; - const [ [width, height] ] = utils.getAdUnitSizes(adUnitCopy); - - before(function () { - adapterManager.enableAnalytics({ - provider: analyticsAdapterName, - options: analyticsOptions - }); - expect(analyticsAdapter.context).is.not.null; - }); - - after(function () { - analyticsAdapter.disableAnalytics(); - expect(analyticsAdapter.context).is.null; - }); - - it(`should return ${ratio} for same-origin`, function (done) { - const el = document.createElement('div'); - el.setAttribute('id', adUnitCopy.code); - - const offset = height * ratio; - const elStub = sinon.stub(el, 'getBoundingClientRect').returns({ - top: 0 - (height - offset), - bottom: height - offset, - left: 0, - right: width - }); - - const querySelectorStub = sinon.stub(document, 'querySelector'); - querySelectorStub.withArgs(`#${adUnitCopy.code}`).returns(el); - - rtdProvider.atf(adUnitCopy, function(x) { - expect(x).is.equal(ratio); - - querySelectorStub.restore(); - elStub.restore(); - - done(); - }); - }); - - ('IntersectionObserver' in window ? it : it.skip)(`should return ${ratio} for cross-origin`, function (done) { - const frameElementStub = sinon.stub(window, 'frameElement').value(null); - - const el = document.createElement('div'); - el.setAttribute('id', adUnitCopy.code); - - const elStub = sinon.stub(el, 'getBoundingClientRect').returns({ - top: 0, - bottom: height, - left: 0, - right: width - }); - - const querySelectorStub = sinon.stub(document, 'querySelector'); - querySelectorStub.withArgs(`#${adUnitCopy.code}`).returns(el); - - let intersectionObserverStubFn = null; - const intersectionObserverStub = sinon.stub(window, 'IntersectionObserver').callsFake((fn) => { - intersectionObserverStubFn = fn; - return { - observe: (element) => { - expect(element).is.equal(el); - - intersectionObserverStubFn([{ - target: element, - intersectionRect: { width, height }, - intersectionRatio: ratio - }]); - }, - unobserve: (element) => { - expect(element).is.equal(el); - } - } - }); - - rtdProvider.atf(adUnitCopy, function(x) { - expect(x).is.equal(ratio); - - intersectionObserverStub.restore(); - querySelectorStub.restore(); - elStub.restore(); - frameElementStub.restore(); - - done(); - }); - }); - }); }); diff --git a/test/spec/modules/admaruBidAdapter_spec.js b/test/spec/modules/admaruBidAdapter_spec.js index a45ddae108f..813a4ed8b29 100644 --- a/test/spec/modules/admaruBidAdapter_spec.js +++ b/test/spec/modules/admaruBidAdapter_spec.js @@ -121,4 +121,52 @@ describe('Admaru Adapter', function () { expect(result.length).to.equal(0); }); }); + + describe('getUserSyncs()', () => { + it('should return iframe user sync if iframe sync is enabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: true, + }, + null + ); + + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://p2.admaru.net/UserSync/sync', + }, + ]); + }); + + it('should return image syncs if they are enabled and iframe is disabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: true, + iframeEnabled: false, + }, + null + ); + + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://p2.admaru.net/UserSync/sync', + }, + ]); + }); + + it('should not return user syncs if syncs are disabled', () => { + const syncs = spec.getUserSyncs( + { + pixelEnabled: false, + iframeEnabled: false, + }, + null + ); + + expect(syncs).to.deep.equal([]); + }); + }); }); diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js new file mode 100644 index 00000000000..65a7b4111b7 --- /dev/null +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -0,0 +1,305 @@ +import {expect} from 'chai'; +import {spec, storage} from 'modules/admaticBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {getStorageManager} from 'src/storageManager'; + +const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb?bidder=admatic'; + +describe('admaticBidAdapter', () => { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function() { + let bid = { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee' + }; + + it('should return true when required params found', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function() { + let bid = Object.assign({}, bid); + delete bid.params; + + bid.params = { + 'networkId': 0, + 'host': 'layer.serve.admatic.com.tr' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via POST', function () { + let validRequest = [ { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + }, + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + }, + 'user': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' + }, + 'blacklist': [], + 'site': { + 'page': 'http://localhost:8888/admatic.html', + 'ref': 'http://localhost:8888', + 'publisher': { + 'name': 'localhost', + 'publisherId': 12321312 + } + }, + 'imp': [ + { + 'size': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 728, + 'h': 90 + } + ], + 'id': '2205da7a81846b', + 'floors': { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + } + } + ], + 'ext': { + 'cur': 'USD', + 'bidder': 'admatic' + } + } ]; + let bidderRequest = { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + }, + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + }, + 'user': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' + }, + 'blacklist': [], + 'site': { + 'page': 'http://localhost:8888/admatic.html', + 'ref': 'http://localhost:8888', + 'publisher': { + 'name': 'localhost', + 'publisherId': 12321312 + } + }, + 'imp': [ + { + 'size': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 728, + 'h': 90 + } + ], + 'id': '2205da7a81846b', + 'floors': { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + } + } + ], + 'ext': { + 'cur': 'USD', + 'bidder': 'admatic' + } + }; + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should properly build a banner request with floors', function () { + let bidRequests = [ + { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + }, + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + } + }, + ]; + let bidderRequest = { + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [728, 90]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + } + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + request.data.imp[0].floors = { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + }; + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid responses', function() { + let bids = { body: { + data: [ + { + 'id': 1, + 'creative_id': '374', + 'width': 300, + 'height': 250, + 'price': 0.01, + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': '
' + } + ], + 'queryId': 'cdnbh24rlv0hhkpfpln0', + 'status': true + }}; + + let expectedResponse = [ + { + requestId: 1, + cpm: 0.01, + width: 300, + height: 250, + currency: 'TRY', + netRevenue: true, + ad: '
', + creativeId: '374', + meta: { + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 360, + bidder: 'admatic' + } + ]; + const request = { + ext: { + 'cur': 'TRY', + 'type': 'admatic' + } + }; + let result = spec.interpretResponse(bids, {data: request}); + expect(result).to.eql(expectedResponse); + }); + + it('handles nobid responses', function () { + let request = { + ext: { + 'cur': 'TRY', + 'type': 'admatic' + } + }; + let bids = { body: { + data: [], + 'queryId': 'cdnbh24rlv0hhkpfpln0', + 'status': true + }}; + + let result = spec.interpretResponse(bids, {data: request}); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 228b87ae4d5..26caaf5b70f 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,8 +4,10 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; +const BIDDER_CODE_ADX = 'admixeradx'; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; +const ENDPOINT_URL_ADX = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; describe('AdmixerAdapter', function () { @@ -16,18 +18,22 @@ describe('AdmixerAdapter', function () { expect(adapter.callBids).to.be.exist.and.to.be.a('function'); }); }); + // inv-nets.admixer.net/adxprebid.1.2.aspx describe('isBidRequestValid', function () { let bid = { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', }; it('should return true when required params found', function () { @@ -38,7 +44,7 @@ describe('AdmixerAdapter', function () { let bid = Object.assign({}, bid); delete bid.params; bid.params = { - 'placementId': 0 + placementId: 0, }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -47,22 +53,25 @@ describe('AdmixerAdapter', function () { describe('buildRequests', function () { let validRequest = [ { - 'bidder': BIDDER_CODE, - 'params': { - 'zone': ZONE_ID + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, ]; let bidderRequest = { bidderCode: BIDDER_CODE, refererInfo: { - page: 'https://example.com' - } + page: 'https://example.com', + }, }; it('should add referrer and imp to be equal bidRequest', function () { @@ -81,49 +90,122 @@ describe('AdmixerAdapter', function () { it('sends bid request to CUSTOM_ENDPOINT via GET', function () { config.setBidderConfig({ bidders: [BIDDER_CODE], // one or more bidders - config: {[BIDDER_CODE]: {endpoint_url: ENDPOINT_URL_CUSTOM}} + config: { [BIDDER_CODE]: { endpoint_url: ENDPOINT_URL_CUSTOM } }, }); - const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest)); + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(validRequest, bidderRequest) + ); expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); expect(request.method).to.equal('POST'); }); }); + describe('buildRequestsAdmixerADX', function () { + let validRequest = [ + { + bidder: BIDDER_CODE_ADX, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE_ADX, + refererInfo: { + page: 'https://example.com', + }, + }; + + it('sends bid request to ADX ENDPOINT', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT_URL_ADX); + expect(request.method).to.equal('POST'); + }); + }); + + describe('checkFloorGetting', function () { + let validRequest = [ + { + bidder: BIDDER_CODE, + params: { + zone: ZONE_ID, + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }, + ]; + let bidderRequest = { + bidderCode: BIDDER_CODE, + refererInfo: { + page: 'https://example.com', + }, + }; + it('gets floor', function () { + bidderRequest.getFloor = () => { + return { floor: 0.6 }; + }; + const request = spec.buildRequests(validRequest, bidderRequest); + const payload = request.data; + expect(payload.bidFloor).to.deep.equal(0.6); + }); + }); + describe('interpretResponse', function () { let response = { body: { - ads: [{ - 'currency': 'USD', - 'cpm': 6.210000, - 'ad': '
ad
', - 'width': 300, - 'height': 600, - 'creativeId': 'ccca3e5e-0c54-4761-9667-771322fbdffc', - 'ttl': 360, - 'netRevenue': false, - 'requestId': '5e4e763b6bc60b', - 'dealId': 'asd123', - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - }] - } + ads: [ + { + currency: 'USD', + cpm: 6.21, + ad: '
ad
', + width: 300, + height: 600, + creativeId: 'ccca3e5e-0c54-4761-9667-771322fbdffc', + ttl: 360, + netRevenue: false, + requestId: '5e4e763b6bc60b', + dealId: 'asd123', + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, + ], + }, }; it('should get correct bid response', function () { const ads = response.body.ads; let expectedResponse = [ { - 'requestId': ads[0].requestId, - 'cpm': ads[0].cpm, - 'creativeId': ads[0].creativeId, - 'width': ads[0].width, - 'height': ads[0].height, - 'ad': ads[0].ad, - 'currency': ads[0].currency, - 'netRevenue': ads[0].netRevenue, - 'ttl': ads[0].ttl, - 'dealId': ads[0].dealId, - 'meta': {'advertiserId': 123, 'networkId': 123, 'advertiserDomains': ['test.com']} - } + requestId: ads[0].requestId, + cpm: ads[0].cpm, + creativeId: ads[0].creativeId, + width: ads[0].width, + height: ads[0].height, + ad: ads[0].ad, + currency: ads[0].currency, + netRevenue: ads[0].netRevenue, + ttl: ads[0].ttl, + dealId: ads[0].dealId, + meta: { + advertiserId: 123, + networkId: 123, + advertiserDomains: ['test.com'], + }, + }, ]; let result = spec.interpretResponse(response); @@ -141,18 +223,16 @@ describe('AdmixerAdapter', function () { describe('getUserSyncs', function () { let imgUrl = 'https://example.com/img1'; let frmUrl = 'https://example.com/frm2'; - let responses = [{ - body: { - cm: { - pixels: [ - imgUrl - ], - iframes: [ - frmUrl - ], - } - } - }]; + let responses = [ + { + body: { + cm: { + pixels: [imgUrl], + iframes: [frmUrl], + }, + }, + }, + ]; it('Returns valid values', function () { let userSyncAll = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: true}, responses); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 2d5ea630f0f..b787a52d6f2 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -71,6 +71,30 @@ describe('adnuntiusBidAdapter', function () { } ] + // const nativeBidderRequest = [ + // { + // bidId: '123', + // bidder: 'adnuntius', + // params: { + // auId: '8b6bc', + // network: 'adnuntius', + // }, + // mediaTypes: { + // native: { + // title: { + // required: true + // }, + // image: { + // required: true + // }, + // body: { + // required: true + // } + // } + // }, + // } + // ] + const singleBidRequest = { bid: [ { @@ -83,6 +107,10 @@ describe('adnuntiusBidAdapter', function () { bid: videoBidderRequest } + // const nativeBidRequest = { + // bid: nativeBidderRequest + // } + const serverResponse = { body: { 'adUnits': [ @@ -209,6 +237,83 @@ describe('adnuntiusBidAdapter', function () { ] } } + // const serverNativeResponse = { + // body: { + // 'adUnits': [ + // { + // 'auId': '000000000008b6bc', + // 'targetId': '123', + // 'html': '

hi!

', + // 'matchedAdCount': 1, + // 'responseId': 'adn-rsp-1460129238', + // 'ads': [ + // { + // 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + // 'assets': { + // 'image': { + // 'cdnId': 'https://assets.adnuntius.com/K9rfXC6wJvgVuy4Fbt5P8oEEGXme9ZaP8BNDzz3OMGQ.jpg', + // 'width': '300', + // 'height': '250' + // } + // }, + // 'text': { + // 'body': { + // 'content': 'Testing Native ad from Adnuntius', + // 'length': '32', + // 'minLength': '0', + // 'maxLength': '100' + // }, + // 'title': { + // 'content': 'Native Ad', + // 'length': '9', + // 'minLength': '5', + // 'maxLength': '100' + // } + // }, + // 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'urls': { + // 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + // }, + // 'urlsEsc': { + // 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + // }, + // 'destinationUrls': { + // 'destination': 'http://google.com' + // }, + // 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, + // 'bid': { 'amount': 0.005, 'currency': 'NOK' }, + // 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + // 'impressionTrackingUrls': [], + // 'impressionTrackingUrlsEsc': [], + // 'adId': 'adn-id-1347343135', + // 'selectedColumn': '0', + // 'selectedColumnPosition': '0', + // 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + // 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + // 'creativeWidth': '980', + // 'creativeHeight': '120', + // 'creativeId': 'wgkq587vgtpchsx1', + // 'lineItemId': 'scyjdyv3mzgdsnpf', + // 'layoutId': 'sw6gtws2rdj1kwby', + // 'layoutName': 'Responsive image' + // }, + + // ] + // }, + // { + // 'auId': '000000000008b6bc', + // 'targetId': '456', + // 'matchedAdCount': 0, + // 'responseId': 'adn-rsp-1460129238', + // } + // ] + // } + // } describe('inherited functions', function () { it('exists and is a function', function () { @@ -426,4 +531,21 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); }); }); + // describe('interpretNativeResponse', function () { + // it('should return valid response when passed valid server response', function () { + // const interpretedResponse = spec.interpretResponse(serverNativeResponse, nativeBidRequest); + // const ad = serverNativeResponse.body.adUnits[0].ads[0] + // expect(interpretedResponse).to.have.lengthOf(1); + // expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); + // expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + // expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + // expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + // expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + // expect(interpretedResponse[0].netRevenue).to.equal(false); + // expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + // expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + // expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); + // expect(interpretedResponse[0].native.body).to.equal(serverNativeResponse.body.adUnits[0].ads[0].text.body.content); + // }); + // }); }); diff --git a/test/spec/modules/aduptechBidAdapter_spec.js b/test/spec/modules/aduptechBidAdapter_spec.js index bbc4e554f7e..8dbdbbfeab5 100644 --- a/test/spec/modules/aduptechBidAdapter_spec.js +++ b/test/spec/modules/aduptechBidAdapter_spec.js @@ -182,6 +182,54 @@ describe('AduptechBidAdapter', () => { }); }); + describe('getFloor', () => { + let bidRequest; + + beforeEach(() => { + bidRequest = { + getFloor: sinon.stub() + }; + }); + + it('should handle empty or invalid bidRequest', () => { + expect(internal.getFloor(null)).to.be.null; + expect(internal.getFloor({})).to.be.null; + expect(internal.getFloor({ getFloor: 'foo' })).to.be.null; + }); + + it('should detect floor via getFloor()', () => { + const result = { + floor: 1.11, + currency: 'USD' + }; + + const options = { + mediaType: BANNER, + size: '*' + } + + bidRequest.getFloor.returns(result); + + expect(internal.getFloor(bidRequest, options)).to.deep.equal(result); + expect(bidRequest.getFloor.calledOnceWith(options)).to.be.true; + }); + + it('should handle empty, invalid or faulty getFloor() results', () => { + bidRequest.getFloor + .onCall(0).returns({}) + .onCall(1).returns({ floor: 'foo' }) + .onCall(2).returns('bar') + .onCall(3).throws(new Error('baz')); + + expect(internal.getFloor(bidRequest, {})).to.be.null; + expect(internal.getFloor(bidRequest, {})).to.be.null; + expect(internal.getFloor(bidRequest, {})).to.be.null; + expect(internal.getFloor(bidRequest, {})).to.be.null; + + expect(bidRequest.getFloor.callCount).to.equal(4); + }); + }); + describe('groupBidRequestsByPublisher', () => { it('should handle empty bidRequests', () => { expect(internal.groupBidRequestsByPublisher(null)).to.deep.equal({}); @@ -541,34 +589,35 @@ describe('AduptechBidAdapter', () => { } }; - const getFloorResponse = { - currency: 'USD', - floor: '1.23' - }; - - const validBidRequests = [ - { - bidId: 'bidId1', - adUnitCode: 'adUnitCode1', - transactionId: 'transactionId1', - mediaTypes: { - banner: { - sizes: [[100, 200], [300, 400]] - } - }, - params: { - publisher: 'publisher1', - placement: 'placement1' + const bidRequest = { + bidId: 'bidId1', + adUnitCode: 'adUnitCode1', + transactionId: 'transactionId1', + mediaTypes: { + banner: { + sizes: [[100, 200], [300, 400]] }, - getFloor: () => { - return getFloorResponse + native: { + image: { + required: true + }, } - } - ]; + }, + params: { + publisher: 'publisher1', + placement: 'placement1' + }, + getFloor: sinon.stub() + .onCall(0).returns({ floor: 1.11, currency: 'USD' }) + .onCall(1).returns({ floor: 2.22, currency: 'EUR' }) + .onCall(2).returns({ floor: 3.33, currency: 'USD' }) + .onCall(3).returns({ floor: 4.44, currency: 'GBP' }) + .onCall(4).returns({ floor: 5.55, currency: 'EUR' }) + }; - expect(spec.buildRequests(validBidRequests, bidderRequest)).to.deep.equal([ + expect(spec.buildRequests([bidRequest], bidderRequest)).to.deep.equal([ { - url: internal.buildEndpointUrl(validBidRequests[0].params.publisher), + url: internal.buildEndpointUrl(bidRequest.params.publisher), method: ENDPOINT_METHOD, data: { auctionId: bidderRequest.auctionId, @@ -580,17 +629,39 @@ describe('AduptechBidAdapter', () => { }, imp: [ { - bidId: validBidRequests[0].bidId, - transactionId: validBidRequests[0].transactionId, - adUnitCode: validBidRequests[0].adUnitCode, - params: validBidRequests[0].params, - banner: validBidRequests[0].mediaTypes.banner, - floorPrice: getFloorResponse.floor + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + adUnitCode: bidRequest.adUnitCode, + params: bidRequest.params, + banner: { + sizes: [ + [100, 200, 1.11, 'USD'], + [300, 400, 2.22, 'EUR'], + ], + floorPrice: 3.33, + floorCurrency: 'USD' + }, + native: { + image: { + required: true + }, + floorPrice: 4.44, + floorCurrency: 'GBP' + }, + floorPrice: 5.55, + floorCurrency: 'EUR' } ] } } ]); + + expect(bidRequest.getFloor.callCount).to.equal(5); + expect(bidRequest.getFloor.getCall(0).calledWith({ mediaType: BANNER, size: bidRequest.mediaTypes.banner.sizes[0] })).to.be.true; + expect(bidRequest.getFloor.getCall(1).calledWith({ mediaType: BANNER, size: bidRequest.mediaTypes.banner.sizes[1] })).to.be.true; + expect(bidRequest.getFloor.getCall(2).calledWith({ mediaType: BANNER, size: '*' })).to.be.true; + expect(bidRequest.getFloor.getCall(3).calledWith({ mediaType: NATIVE, size: '*' })).to.be.true; + expect(bidRequest.getFloor.getCall(4).calledWith({ mediaType: '*', size: '*' })).to.be.true; }); }); diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index bab0776a5f4..24cede20352 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -18,7 +18,7 @@ describe('Adyoulike Adapter', function () { consentString: consentString, gdprApplies: true }, - refererInfo: {location: referrerUrl, canonicalUrl, domain}, + refererInfo: {location: referrerUrl, canonicalUrl, domain, topmostLocation: 'fakePageURL'}, ortb2: {site: {page: pageUrl, ref: referrerUrl}} }; const bidRequestWithEmptyPlacement = [ @@ -722,6 +722,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -736,6 +737,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); @@ -758,6 +760,7 @@ describe('Adyoulike Adapter', function () { expect(payload.Bids['bid_id_1'].TransactionID).to.be.equal('bid_id_1_transaction_id'); expect(payload.Bids['bid_id_3'].TransactionID).to.be.equal('bid_id_3_transaction_id'); expect(payload.PageRefreshed).to.equal(false); + expect(payload.pbjs_version).to.equal('$prebid.version$'); }); it('sends bid request to endpoint setted by parameters', function () { diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js new file mode 100644 index 00000000000..f58e49eb364 --- /dev/null +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -0,0 +1,739 @@ +import {expect} from 'chai'; +import {setEndPoints, spec} from 'modules/aidemBidAdapter.js'; +import * as utils from '../../../src/utils'; +import {deepSetValue} from '../../../src/utils'; +import {server} from '../../mocks/xhr'; +import {config} from '../../../src/config'; +import {NATIVE} from '../../../src/mediaTypes.js'; + +// Full banner + Full Video + Basic Banner + Basic Video +const VALID_BIDS = [ + { + bidder: 'aidem', + params: { + siteId: '301491', + publisherId: '3021491', + placementId: '13144370', + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + }, + { + bidder: 'aidem', + params: { + siteId: '301491', + publisherId: '3021491', + placementId: '13144370', + }, + mediaTypes: { + video: { + context: 'instream', + minduration: 7, + maxduration: 30, + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [2] + } + }, + }, +] + +const INVALID_BIDS = [ + { + bidder: 'aidem', + params: { + siteId: '3014912' + } + }, + { + bidder: 'aidem', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + params: { + siteId: '3014912', + } + }, + { + bidder: 'aidem', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + params: { + publisherId: '3014912', + } + }, + { + bidder: 'aidem', + params: { + siteId: '3014912', + member: '301e4912' + } + }, + { + bidder: 'aidem', + params: { + siteId: '3014912', + invCode: '3014912' + } + }, + { + bidder: 'aidem', + mediaType: NATIVE, + params: { + siteId: '3014912' + } + }, + { + bidder: 'aidem', + mediaTypes: { + banner: {} + }, + }, + { + bidder: 'aidem', + mediaTypes: { + video: { + placement: 1, + minduration: 7, + maxduration: 30, + mimes: ['video/mp4'], + protocols: [2] + } + }, + params: { + siteId: '301491', + placementId: '13144370', + }, + }, + { + bidder: 'aidem', + mediaTypes: { + video: { + minduration: 7, + maxduration: 30, + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [2] + } + }, + params: { + siteId: '301491', + placementId: '13144370', + }, + }, + { + bidder: 'aidem', + mediaTypes: { + video: { + minduration: 7, + maxduration: 30, + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [2], + placement: 1 + } + }, + params: { + siteId: '301491', + placementId: '13144370', + video: { + size: [480, 40] + } + }, + }, +] + +const DEFAULT_VALID_BANNER_REQUESTS = [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'aidem', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { + sizes: [ + [ 300, 250 ], + [ 300, 600 ] + ] + } + }, + params: { + siteId: '1', + placementId: '13144370' + }, + src: 'client', + transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' + } +]; + +const DEFAULT_VALID_VIDEO_REQUESTS = [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'aidem', + bidderRequestId: '15246a574e859f', + mediaTypes: { + video: { + minduration: 7, + maxduration: 30, + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [2] + } + }, + params: { + siteId: '1', + placementId: '13144370' + }, + src: 'client', + transactionId: '54a58774-7a41-494e-9aaf-fa7b79164f0c' + } +]; + +const VALID_BIDDER_REQUEST = { + auctionId: '6e9b46c3-65a8-46ea-89f4-c5071110c85c', + bidderCode: 'aidem', + bidderRequestId: '170ea5d2b1d073', + refererInfo: { + page: 'test-page', + domain: 'test-domain', + ref: 'test-referer' + }, +} + +// Add mediatype +const SERVER_RESPONSE_BANNER = { + body: { + id: 'efa1930a-bc3e-4fd0-8368-08bc40236b4f', + bid: [ + // BANNER + { + 'id': '2e614be960ee1d', + 'impid': '2e614be960ee1d', + 'price': 7.91, + 'mediatype': 'banner', + 'adid': '24277955', + 'adm': 'creativity_banner', + 'adomain': [ + 'aidem.com' + ], + 'iurl': 'http://www.aidem.com', + 'cat': [], + 'cid': '4193561', + 'crid': '24277955', + 'w': 300, + 'h': 250, + 'ext': { + 'dspid': 85, + 'advbrandid': 1246, + 'advbrand': 'AIDEM' + } + }, + ], + cur: 'USD' + }, +} + +const SERVER_RESPONSE_VIDEO = { + body: { + id: 'efa1930a-bc3e-4fd0-8368-08bc40236b4f', + bid: [ + // VIDEO + { + 'id': '2876a29392a47c', + 'impid': '2876a29392a47c', + 'price': 7.93, + 'mediatype': 'video', + 'adid': '24277955', + 'adm': 'https://hermes.aidemsrv.com/vast-tag/cl9mzhhd502uq09l720uegb02?auction_id={{AUCTION_ID}}&cachebuster={{CACHEBUSTER}}', + 'adomain': [ + 'aidem.com' + ], + 'iurl': 'http://www.aidem.com', + 'cat': [], + 'cid': '4193561', + 'crid': '24277955', + 'w': 640, + 'h': 480, + 'ext': { + 'dspid': 85, + 'advbrandid': 1246, + 'advbrand': 'AIDEM' + } + } + ], + cur: 'USD' + }, +} + +const WIN_NOTICE = { + 'adId': '3a20ee5dc78c1e', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'creativeId': '24277955', + 'cpm': 1, + 'netRevenue': false, + 'adserverTargeting': { + 'hb_bidder': 'aidem', + 'hb_adid': '3a20ee5dc78c1e', + 'hb_pb': '1.00', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + + 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', + 'currency': [ + 'USD' + ], + 'mediaType': 'banner', + 'advertiserDomains': [ + 'abc.com' + ], + 'size': '300x250', + 'params': [ + { + 'placementId': '13144370', + 'siteId': '23434', + 'publisherId': '7689670753' + } + ], + 'width': 300, + 'height': 250, + 'status': 'rendered', + 'transactionId': 'ce089116-4251-45c3-bdbb-3a03cb13816b', + 'ttl': 300, + 'requestTimestamp': 1666796241007, + 'responseTimestamp': 1666796241021, + metrics: { + getMetrics() { + return { + + } + } + } +} + +const ERROR_NOTICE = { + 'message': 'Prebid.js: Server call for aidem failed.', + 'url': 'http%3A%2F%2Flocalhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue', + 'auctionId': 'b57faab7-23f7-4b63-90db-67b259d20db7', + 'bidderRequestId': '1c53857d1ce616', + 'timeout': 1000, + 'bidderCode': 'aidem', + metrics: { + getMetrics() { + return { + + } + } + } +} + +describe('Aidem adapter', () => { + describe('isBidRequestValid', () => { + it('should return true for each valid bid requests', function () { + // spec.isBidRequestValid() + VALID_BIDS.forEach((value, index) => { + expect(spec.isBidRequestValid(value)).to.be.true + }) + }); + + it('should return false for each invalid bid requests', function () { + // spec.isBidRequestValid() + INVALID_BIDS.forEach((value, index) => { + expect(spec.isBidRequestValid(value)).to.be.false + }) + }); + + it('should return true if valid banner sizes are specified in params as single array', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'banner.size', [300, 250]) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.true + }); + + it('should return true if valid banner sizes are specified in params as array of array', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'banner.size', [[300, 600]]) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.true + }); + + it('should return true if valid video sizes are specified in params as single array', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'video.size', [640, 480]) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.true + }); + + it('BANNER: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.true + }); + + it('BANNER: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('BANNER: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[0]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return true if rateLimit is 1', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 1) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.true + }); + + it('VIDEO: should return false if rateLimit is 0', function () { + // spec.isBidRequestValid() + const validVideoRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validVideoRequest.params, 'rateLimit', 0) + expect(spec.isBidRequestValid(validVideoRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not between 0 and 1', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', 1.2) + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + + it('VIDEO: should return false if rateLimit is not a number', function () { + // spec.isBidRequestValid() + const validBannerRequest = utils.deepClone(VALID_BIDS[1]) + deepSetValue(validBannerRequest.params, 'rateLimit', '0.5') + expect(spec.isBidRequestValid(validBannerRequest)).to.be.false + }); + }); + + describe('buildRequests', () => { + it('should match server requirements', () => { + const requests = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + expect(requests).to.be.an('object'); + expect(requests.method).to.be.a('string') + expect(requests.data).to.be.a('string') + expect(requests.options).to.be.an('object').that.have.a.property('withCredentials') + }); + + it('should have a well formatted banner payload', () => { + const requests = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + const payload = JSON.parse(requests.data) + expect(payload).to.be.a('object').that.has.all.keys( + 'id', 'imp', 'device', 'cur', 'tz', 'regs', 'site', 'environment', 'at' + ) + expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_BANNER_REQUESTS.length) + + expect(payload.imp[0]).to.be.a('object').that.has.all.keys( + 'banner', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' + ) + expect(payload.imp[0].banner).to.be.a('object').that.has.all.keys( + 'format', 'topframe' + ) + }); + + it('should have a well formatted video payload', () => { + const requests = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); + const payload = JSON.parse(requests.data) + expect(payload).to.be.a('object').that.has.all.keys( + 'id', 'imp', 'device', 'cur', 'tz', 'regs', 'site', 'environment', 'at' + ) + expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_VIDEO_REQUESTS.length) + + expect(payload.imp[0]).to.be.a('object').that.has.all.keys( + 'video', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' + ) + expect(payload.imp[0].video).to.be.a('object').that.has.all.keys( + 'format', 'mimes', 'minDuration', 'maxDuration', 'protocols' + ) + }); + + it('should have a well formatted bid floor payload if configured', () => { + const validBannerRequests = utils.deepClone(DEFAULT_VALID_BANNER_REQUESTS) + validBannerRequests[0].params.floor = { + value: 1.98, + currency: 'USD' + } + const requests = spec.buildRequests(validBannerRequests, VALID_BIDDER_REQUEST); + const payload = JSON.parse(requests.data) + const { floor } = payload.imp[0] + expect(floor).to.be.a('object').that.has.all.keys( + 'value', 'currency' + ) + }); + + it('should hav wpar keys in environment object', function () { + const requests = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); + const payload = JSON.parse(requests.data) + expect(payload).to.have.property('environment') + expect(payload.environment).to.be.a('object').that.have.property('wpar') + expect(payload.environment.wpar).to.be.a('object').that.has.keys('innerWidth', 'innerHeight') + }); + }) + + describe('interpretResponse', () => { + it('should return a valid bid array with a banner bid', () => { + const response = utils.deepClone(SERVER_RESPONSE_BANNER) + const interpreted = spec.interpretResponse(response) + expect(interpreted).to.be.a('array').that.has.lengthOf(1) + interpreted.forEach(value => { + expect(value).to.be.a('object').that.has.all.keys( + 'ad', 'cpm', 'creativeId', 'currency', 'height', 'mediaType', 'meta', 'netRevenue', 'requestId', 'ttl', 'width', 'dealId' + ) + }) + }); + + it('should return a valid bid array with a banner bid', () => { + const response = utils.deepClone(SERVER_RESPONSE_VIDEO) + const interpreted = spec.interpretResponse(response) + expect(interpreted).to.be.a('array').that.has.lengthOf(1) + interpreted.forEach(value => { + expect(value).to.be.a('object').that.has.all.keys( + 'vastUrl', 'cpm', 'creativeId', 'currency', 'height', 'mediaType', 'meta', 'netRevenue', 'requestId', 'ttl', 'width', 'dealId' + ) + }) + }); + + it('should return a valid bid array with netRevenue', () => { + const response = utils.deepClone(SERVER_RESPONSE_BANNER) + response.body.bid[0].isNet = true + const interpreted = spec.interpretResponse(response) + expect(interpreted).to.be.a('array').that.has.lengthOf(1) + expect(interpreted[0].netRevenue).to.be.true + }); + + it('should return an empty bid array if one of seatbid entry is missing price property', () => { + const response = utils.deepClone(SERVER_RESPONSE_BANNER) + delete response.body.bid[0].price + const interpreted = spec.interpretResponse(response) + expect(interpreted).to.be.a('array').that.has.lengthOf(0) + }); + + it('should return an empty bid array if one of seatbid entry is missing adm property', () => { + const response = utils.deepClone(SERVER_RESPONSE_BANNER) + delete response.body.bid[0].adm + const interpreted = spec.interpretResponse(response) + expect(interpreted).to.be.a('array').that.has.lengthOf(0) + }); + }) + + describe('onBidWon', () => { + it(`should exists and type function`, function () { + expect(spec.onBidWon).to.exist.and.to.be.a('function') + }); + + it(`should send a valid bid won notice`, function () { + spec.onBidWon(WIN_NOTICE); + // server.respondWith('POST', WIN_EVENT_URL, [ + // 400, {'Content-Type': 'application/json'}, ) + // ]); + expect(server.requests.length).to.equal(1); + }); + }); + + describe('onBidderError', () => { + it(`should exists and type function`, function () { + expect(spec.onBidderError).to.exist.and.to.be.a('function') + }); + + it(`should send a valid error notice`, function () { + spec.onBidderError({ bidderRequest: ERROR_NOTICE }) + expect(server.requests.length).to.equal(1); + const body = JSON.parse(server.requests[0].requestBody) + expect(body).to.be.a('object').that.has.all.keys('message', 'auctionId', 'bidderRequestId', 'url', 'metrics') + // const { bids } = JSON.parse(server.requests[0].requestBody) + // expect(bids).to.be.a('array').that.has.lengthOf(1) + // _each(bids, (bid) => { + // expect(bid).to.be.a('object').that.has.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'transactionId', 'metrics') + // }) + }); + }); + + describe('setEndPoints', () => { + it(`should exists and type function`, function () { + expect(setEndPoints).to.exist.and.to.be.a('function') + }); + + it(`should not modify default endpoints`, function () { + const endpoints = setEndPoints() + const requestURL = new URL(endpoints.request) + const winNoticeURL = new URL(endpoints.notice.win) + const timeoutNoticeURL = new URL(endpoints.notice.timeout) + const errorNoticeURL = new URL(endpoints.notice.error) + + expect(requestURL.host).to.equal('zero.aidemsrv.com') + expect(winNoticeURL.host).to.equal('zero.aidemsrv.com') + expect(timeoutNoticeURL.host).to.equal('zero.aidemsrv.com') + expect(errorNoticeURL.host).to.equal('zero.aidemsrv.com') + + expect(decodeURIComponent(requestURL.pathname)).to.equal('/bid/request') + expect(decodeURIComponent(winNoticeURL.pathname)).to.equal('/notice/win') + expect(decodeURIComponent(timeoutNoticeURL.pathname)).to.equal('/notice/timeout') + expect(decodeURIComponent(errorNoticeURL.pathname)).to.equal('/notice/error') + }); + + it(`should not change request endpoint`, function () { + const endpoints = setEndPoints('default') + const requestURL = new URL(endpoints.request) + expect(decodeURIComponent(requestURL.pathname)).to.equal('/bid/request') + }); + + it(`should change to local env`, function () { + const endpoints = setEndPoints('local') + const requestURL = new URL(endpoints.request) + const winNoticeURL = new URL(endpoints.notice.win) + const timeoutNoticeURL = new URL(endpoints.notice.timeout) + const errorNoticeURL = new URL(endpoints.notice.error) + + expect(requestURL.host).to.equal('127.0.0.1:8787') + expect(winNoticeURL.host).to.equal('127.0.0.1:8787') + expect(timeoutNoticeURL.host).to.equal('127.0.0.1:8787') + expect(errorNoticeURL.host).to.equal('127.0.0.1:8787') + }); + + it(`should add a path prefix`, function () { + const endpoints = setEndPoints('local', '/path') + const requestURL = new URL(endpoints.request) + const winNoticeURL = new URL(endpoints.notice.win) + const timeoutNoticeURL = new URL(endpoints.notice.timeout) + const errorNoticeURL = new URL(endpoints.notice.error) + + expect(decodeURIComponent(requestURL.pathname)).to.equal('/path/bid/request') + expect(decodeURIComponent(winNoticeURL.pathname)).to.equal('/path/notice/win') + expect(decodeURIComponent(timeoutNoticeURL.pathname)).to.equal('/path/notice/timeout') + expect(decodeURIComponent(errorNoticeURL.pathname)).to.equal('/path/notice/error') + }); + + it(`should add a path prefix and change request endpoint`, function () { + const endpoints = setEndPoints('local', '/path') + const requestURL = new URL(endpoints.request) + const winNoticeURL = new URL(endpoints.notice.win) + const timeoutNoticeURL = new URL(endpoints.notice.timeout) + const errorNoticeURL = new URL(endpoints.notice.error) + + expect(decodeURIComponent(requestURL.pathname)).to.equal('/path/bid/request') + expect(decodeURIComponent(winNoticeURL.pathname)).to.equal('/path/notice/win') + expect(decodeURIComponent(timeoutNoticeURL.pathname)).to.equal('/path/notice/timeout') + expect(decodeURIComponent(errorNoticeURL.pathname)).to.equal('/path/notice/error') + }); + }); + + describe('config', () => { + beforeEach(() => { + config.setConfig({ + aidem: { + env: 'main' + } + }); + }) + + it(`should not override default endpoints`, function () { + config.setConfig({ + aidem: { + env: 'unknown', + path: '/test' + } + }); + const { url } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + const requestURL = new URL(url) + expect(requestURL.host).to.equal('zero.aidemsrv.com') + }); + + it(`should set local endpoints`, function () { + config.setConfig({ + aidem: { + env: 'local', + path: '/test' + } + }); + const { url } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + const requestURL = new URL(url) + expect(requestURL.host).to.equal('127.0.0.1:8787') + }); + + it(`should set coppa`, function () { + config.setConfig({ + coppa: true + }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + const request = JSON.parse(data) + expect(request.regs.coppa_applies).to.equal(true) + }); + + it(`should set gdpr to true`, function () { + config.setConfig({ + consentManagement: { + gdpr: { + // any data here set gdpr to true + }, + } + }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + const request = JSON.parse(data) + expect(request.regs.gdpr_applies).to.equal(true) + }); + + it(`should set usp_consent string`, function () { + config.setConfig({ + consentManagement: { + usp: { + cmpApi: 'static', + consentData: { + getUSPData: { + uspString: '1YYY' + } + } + } + } + }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + const request = JSON.parse(data) + expect(request.regs.us_privacy).to.equal('1YYY') + }); + + it(`should not set usp_consent string`, function () { + config.setConfig({ + consentManagement: { + usp: { + cmpApi: 'iab', + consentData: { + getUSPData: { + uspString: '1YYY' + } + } + } + } + }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + const request = JSON.parse(data) + expect(request.regs.us_privacy).to.undefined + }); + }); +}); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index a2095d52857..7cf5698f7d4 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -45,6 +45,26 @@ describe('AjaAdapter', function () { bidId: '30b31c1838de1e', bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'Android', + version: ['8', '0', '0'] + }, + browsers: [ + {brand: 'Not_A Brand', version: ['99', '0', '0', '0']}, + {brand: 'Google Chrome', version: ['109', '0', '5414', '119']}, + {brand: 'Chromium', version: ['109', '0', '5414', '119']} + ], + mobile: 1, + model: 'SM-G955U', + bitness: '64', + architecture: '' + } + } + } } ]; @@ -58,7 +78,7 @@ describe('AjaAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); - expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&'); + expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&sua=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22Android%22%2C%22version%22%3A%5B%228%22%2C%220%22%2C%220%22%5D%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Not_A%20Brand%22%2C%22version%22%3A%5B%2299%22%2C%220%22%2C%220%22%2C%220%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%5D%2C%22mobile%22%3A1%2C%22model%22%3A%22SM-G955U%22%2C%22bitness%22%3A%2264%22%2C%22architecture%22%3A%22%22%7D&'); }); }); diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 1ae9bb56df4..92e5d28f42a 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -108,7 +108,7 @@ describe('alkimiBidAdapter', function () { describe('buildRequests', function () { let bidRequests = [REQUEST] - const bidderRequest = spec.buildRequests(bidRequests, { + let requestData = { auctionId: '123', refererInfo: { page: 'http://test.com/path.html' @@ -119,7 +119,8 @@ describe('alkimiBidAdapter', function () { gdprApplies: true }, uspConsent: 'uspConsent' - }) + } + const bidderRequest = spec.buildRequests(bidRequests, requestData) it('should return a properly formatted request with eids defined', function () { expect(bidderRequest.data.eids).to.deep.equal(REQUEST.userIdAsEids) @@ -138,7 +139,7 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') - expect(bidderRequest.data.schain).to.deep.contains({ver: '1.0', complete: 1, nodes: [{asi: 'alkimi-onboarding.com', sid: '00001', hp: 1}]}) + expect(bidderRequest.data.schain).to.deep.contains({ ver: '1.0', complete: 1, nodes: [{ asi: 'alkimi-onboarding.com', sid: '00001', hp: 1 }] }) expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner', adUnitCode: 'bannerAdUnitCode' }) expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') @@ -147,6 +148,17 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.options.contentType).to.equal('application/json') expect(bidderRequest.url).to.equal(ENDPOINT) }) + + it('sends bidFloor when configured', () => { + const requestWithFloor = Object.assign({}, REQUEST); + requestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && arg.mediaType === 'banner' && JSON.stringify(arg.size) === JSON.stringify([300, 250])) { + return { currency: 'USD', floor: 0.3 } + } + } + const bidderRequestFloor = spec.buildRequests([requestWithFloor], requestData); + expect(bidderRequestFloor.data.signRequest.bids[0].bidFloor).to.be.equal(0.3); + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 50622180ad0..92246f76c7a 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -81,20 +81,46 @@ describe('AolAdapter', function () { const ONE_DISPLAY_TTL = 60; const ONE_MOBILE_TTL = 3600; const SUPPORTED_USER_ID_SOURCES = { - 'adserver.org': '100', - 'criteo.com': '200', - 'id5-sync.com': '300', - 'intentiq.com': '400', - 'liveintent.com': '500', - 'quantcast.com': '600', - 'verizonmedia.com': '700', - 'liveramp.com': '800' + 'admixer.net': '100', + 'adserver.org': '200', + 'adtelligent.com': '300', + 'amxdt.net': '500', + 'audigent.com': '600', + 'britepool.com': '700', + 'criteo.com': '800', + 'crwdcntrl.net': '900', + 'deepintent.com': '1000', + 'epsilon.com': '1100', + 'hcn.health': '1200', + 'id5-sync.com': '1300', + 'idx.lat': '1400', + 'intentiq.com': '1500', + 'intimatemerger.com': '1600', + 'liveintent.com': '1700', + 'liveramp.com': '1800', + 'mediawallahscript.com': '1900', + 'netid.de': '2100', + 'neustar.biz': '2200', + 'pubcid.org': '2600', + 'quantcast.com': '2700', + 'tapad.com': '2800', + 'zeotap.com': '3200' }; const USER_ID_DATA = { + admixerId: SUPPORTED_USER_ID_SOURCES['admixer.net'], + adtelligentId: SUPPORTED_USER_ID_SOURCES['adtelligent.com'], + amxId: SUPPORTED_USER_ID_SOURCES['amxdt.net'], + britepoolid: SUPPORTED_USER_ID_SOURCES['britepool.com'], criteoId: SUPPORTED_USER_ID_SOURCES['criteo.com'], connectid: SUPPORTED_USER_ID_SOURCES['verizonmedia.com'], + dmdId: SUPPORTED_USER_ID_SOURCES['hcn.health'], + hadronId: SUPPORTED_USER_ID_SOURCES['audigent.com'], + lotamePanoramaId: SUPPORTED_USER_ID_SOURCES['crwdcntrl.net'], + deepintentId: SUPPORTED_USER_ID_SOURCES['deepintent.com'], + fabrickId: SUPPORTED_USER_ID_SOURCES['neustar.biz'], idl_env: SUPPORTED_USER_ID_SOURCES['liveramp.com'], + IDP: SUPPORTED_USER_ID_SOURCES['zeotap.com'], lipb: { lipbid: SUPPORTED_USER_ID_SOURCES['liveintent.com'], segments: ['100', '200'] @@ -104,8 +130,15 @@ describe('AolAdapter', function () { uid: SUPPORTED_USER_ID_SOURCES['id5-sync.com'], ext: {foo: 'bar'} }, + idx: SUPPORTED_USER_ID_SOURCES['idx.lat'], + imuid: SUPPORTED_USER_ID_SOURCES['intimatemerger.com'], intentIqId: SUPPORTED_USER_ID_SOURCES['intentiq.com'], - quantcastId: SUPPORTED_USER_ID_SOURCES['quantcast.com'] + mwOpenLinkId: SUPPORTED_USER_ID_SOURCES['mediawallahscript.com'], + netId: SUPPORTED_USER_ID_SOURCES['netid.de'], + quantcastId: SUPPORTED_USER_ID_SOURCES['quantcast.com'], + publinkId: SUPPORTED_USER_ID_SOURCES['epsilon.com'], + pubcid: SUPPORTED_USER_ID_SOURCES['pubcid.org'], + tapadId: SUPPORTED_USER_ID_SOURCES['tapad.com'] }; function createCustomBidRequest({bids, params} = {}) { @@ -455,6 +488,20 @@ describe('AolAdapter', function () { expect(request.url).to.contain(NEXAGE_URL + 'dcn=2c9d2b50015c5ce9db6aeeed8b9500d6&pos=header'); }); + it('should return One Mobile url with configured GPP data', function () { + let bidRequest = createCustomBidRequest({ + params: getNexageGetBidParams() + }); + bidRequest.ortb2 = { + regs: { + gpp: 'testgpp', + gpp_sid: [8] + } + } + let [request] = spec.buildRequests(bidRequest.bids, bidRequest); + expect(request.url).to.contain('gpp=testgpp&gpp_sid=8'); + }); + it('should return One Mobile url with cmd=bid option', function () { let bidRequest = createCustomBidRequest({ params: getNexageGetBidParams() @@ -761,6 +808,14 @@ describe('AolAdapter', function () { 'euconsent=test-consent;gdpr=1;us_privacy=test-usp-consent;'); }); + it('should return formatted gpp privacy params when formatConsentData returns GPP data', function () { + formatConsentDataStub.returns({ + gpp: 'gppstring', + gpp_sid: [6, 7] + }); + expect(spec.formatMarketplaceDynamicParams()).to.be.equal('gpp=gppstring;gpp_sid=6%2C7;'); + }); + it('should return formatted params when formatKeyValues returns data', function () { formatKeyValuesStub.returns({ param1: 'val1', diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 87a4a0fbe2d..9e88e2875c7 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -4,6 +4,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import * as bidderFactory from 'src/adapters/bidderFactory.js'; import { auctionManager } from 'src/auctionManager.js'; import { deepClone } from 'src/utils.js'; +import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -35,14 +36,31 @@ describe('AppNexusAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let bid1 = deepClone(bid); + bid1.params = { + 'placement_id': 123423 + } + expect(spec.isBidRequestValid(bid1)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid1 = deepClone(bid); + bid1.params = { 'member': '1234', 'invCode': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(bid1)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid1 = deepClone(bid); + bid1.params = { + 'member': '1234', + 'inv_code': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid1)).to.equal(true); }); it('should return false when required params are not passed', function () { @@ -53,6 +71,15 @@ describe('AppNexusAdapter', function () { }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placement_id': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -100,6 +127,24 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); }); + it('should parse out private sizes', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + private_sizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); + }); + it('should add position in request', function () { // set from bid.params let bidRequest = deepClone(bidRequests[0]); @@ -167,6 +212,24 @@ describe('AppNexusAdapter', function () { expect(payload.publisher_id).to.deep.equal(1231234); }); + it('should add publisher_id in request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placement_id: '10433394', + publisher_id: '1231234' + } + }); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].publisher_id).to.exist; + expect(payload.tags[0].publisher_id).to.deep.equal(1231234); + expect(payload.publisher_id).to.exist; + expect(payload.publisher_id).to.deep.equal(1231234); + }); + it('should add source and verison to the tag', function () { const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); @@ -188,7 +251,7 @@ describe('AppNexusAdapter', function () { bids: [{ bidder: 'appnexus', params: { - placementId: '10433394' + placement_id: '10433394' } }], transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' @@ -350,7 +413,7 @@ describe('AppNexusAdapter', function () { bidRequests[0], { params: { - placementId: '10433394', + placement_id: '10433394', user: { externalUid: '123', segments: [123, { id: 987, value: 876 }], @@ -370,6 +433,29 @@ describe('AppNexusAdapter', function () { }); }); + it('should add debug params from query', function () { + let getParamStub = sinon.stub(utils, 'getParameterByName').callsFake(function(par) { + if (par === 'apn_debug_dongle') return 'abcdef'; + if (par === 'apn_debug_member_id') return '1234'; + if (par === 'apn_debug_timeout') return '1000'; + + return ''; + }); + + let bidRequest = deepClone(bidRequests[0]); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.debug).to.exist.and.to.deep.equal({ + 'dongle': 'abcdef', + 'enabled': true, + 'member_id': 1234, + 'debug_timeout': 1000 + }); + + getParamStub.restore(); + }); + it('should attach reserve param when either bid param or getFloor function exists', function () { let getFloorResponse = { currency: 'USD', floor: 3 }; let request, payload = null; @@ -383,7 +469,7 @@ describe('AppNexusAdapter', function () { // 2 -> reserve is defined, getFloor not defined > reserve is used bidRequest.params = { - 'placementId': '10433394', + 'placement_id': '10433394', 'reserve': 0.5 }; request = spec.buildRequests([bidRequest]); @@ -404,7 +490,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -437,7 +523,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -460,7 +546,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -502,7 +588,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -533,7 +619,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -561,7 +647,7 @@ describe('AppNexusAdapter', function () { let bidRequest = Object.assign({}, bidRequests[0], { - params: { placementId: '14542875' } + params: { placement_id: '14542875' } }, { mediaTypes: { @@ -586,7 +672,7 @@ describe('AppNexusAdapter', function () { mediaType: 'banner', params: { sizes: [[300, 250], [300, 600]], - placementId: 13144370 + placement_id: 13144370 } } ); @@ -762,7 +848,7 @@ describe('AppNexusAdapter', function () { bidRequests[0], { params: { - placementId: '10433394', + placement_id: '10433394', keywords: { single: 'val', singleArr: ['val'], @@ -907,6 +993,23 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].use_pmt_rule).to.equal(true); }); + it('should add payment rules to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placement_id: '10433394', + use_payment_rule: true + } + } + ); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); + it('should add gpid to the request', function () { let testGpid = '/12345/my-gpt-tag-0'; let bidRequest = deepClone(bidRequests[0]); @@ -961,6 +1064,52 @@ describe('AppNexusAdapter', function () { expect(payload.us_privacy).to.exist.and.to.equal(consentString); }); + it('should add gpp information to the request via bidderRequest.gppConsent', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': [8] + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.privacy).to.exist; + expect(payload.privacy.gpp).to.equal(consentString); + expect(payload.privacy.gpp_sid).to.deep.equal([8]); + }); + + it('should add gpp information to the request via bidderRequest.ortb2.regs', function () { + let consentString = 'abc1234'; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'ortb2': { + 'regs': { + 'gpp': consentString, + 'gpp_sid': [7] + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.privacy).to.exist; + expect(payload.privacy.gpp).to.equal(consentString); + expect(payload.privacy.gpp_sid).to.deep.equal([7]); + }); + it('supports sending hybrid mobile app parameters', function () { let appRequest = Object.assign({}, bidRequests[0], @@ -1169,9 +1318,33 @@ describe('AppNexusAdapter', function () { source: 'puburl2.com', uids: [{ id: 'pubid2' + }, { + id: 'pubid2-123' }] }] - } + }, + userIdAsEids: [{ + source: 'adserver.org', + uids: [{ id: 'sample-userid' }] + }, { + source: 'criteo.com', + uids: [{ id: 'sample-criteo-userid' }] + }, { + source: 'netid.de', + uids: [{ id: 'sample-netId-userid' }] + }, { + source: 'liveramp.com', + uids: [{ id: 'sample-idl-userid' }] + }, { + source: 'uidapi.com', + uids: [{ id: 'sample-uid2-value' }] + }, { + source: 'puburl.com', + uids: [{ id: 'pubid1' }] + }, { + source: 'puburl2.com', + uids: [{ id: 'pubid2' }, { id: 'pubid2-123' }] + }] }); const request = spec.buildRequests([bidRequest]); @@ -1212,6 +1385,10 @@ describe('AppNexusAdapter', function () { source: 'puburl2.com', id: 'pubid2' }); + expect(payload.eids).to.deep.include({ + source: 'puburl2.com', + id: 'pubid2-123' + }); }); it('should populate iab_support object at the root level if omid support is detected', function () { @@ -1560,7 +1737,10 @@ describe('AppNexusAdapter', function () { 'phone': '1234567890', 'address': '28 W 23rd St, New York, NY 10010', 'privacy_link': 'https://appnexus.com/?url=privacy_url', - 'javascriptTrackers': '' + 'javascriptTrackers': '', + 'video': { + 'content': '' + } }; let bidderRequest = { bids: [{ @@ -1574,6 +1754,7 @@ describe('AppNexusAdapter', function () { expect(result[0].native.body).to.equal('Cool description great stuff'); expect(result[0].native.cta).to.equal('Do it'); expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + expect(result[0].native.video.content).to.equal(''); }); } @@ -1661,7 +1842,7 @@ describe('AppNexusAdapter', function () { it('should add advertiserDomains', function () { let responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + responseAdvertiserId.tags[0].ads[0].adomain = '123'; let bidderRequest = { bids: [{ @@ -1671,7 +1852,7 @@ describe('AppNexusAdapter', function () { } let result = spec.interpretResponse({ body: responseAdvertiserId }, { bidderRequest }); expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); - expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + expect(result[0].meta.advertiserDomains).to.deep.equal(['123']); }); }); diff --git a/test/spec/modules/appushBidAdapter_spec.js b/test/spec/modules/appushBidAdapter_spec.js new file mode 100644 index 00000000000..91c50cc3dd0 --- /dev/null +++ b/test/spec/modules/appushBidAdapter_spec.js @@ -0,0 +1,372 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/appushBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'appush' + +describe('AppushBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://hb.appush.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/arcspanRtdProvider_spec.js b/test/spec/modules/arcspanRtdProvider_spec.js new file mode 100644 index 00000000000..c75075d8e05 --- /dev/null +++ b/test/spec/modules/arcspanRtdProvider_spec.js @@ -0,0 +1,187 @@ +import { arcspanSubmodule } from 'modules/arcspanRtdProvider.js'; +import { expect } from 'chai'; +import { loadExternalScript } from 'src/adloader.js'; + +describe('arcspanRtdProvider', function () { + describe('init', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('successfully initializes with a valid silo ID', function () { + expect(arcspanSubmodule.init(getGoodConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://silo13.p7cloud.net/as.js'); + loadExternalScript.resetHistory(); + }); + + it('fails to initialize with a missing silo ID', function () { + expect(arcspanSubmodule.init(getBadConfig())).to.equal(false); + expect(loadExternalScript.called).to.be.not.ok; + loadExternalScript.resetHistory(); + }); + + it('drops localhost script for test silo', function () { + expect(arcspanSubmodule.init(getTestConfig())).to.equal(true); + expect(loadExternalScript.called).to.be.ok; + expect(loadExternalScript.args[0][0]).to.deep.equal('https://localhost:8080/as.js'); + loadExternalScript.resetHistory(); + }); + }); + + describe('alterBidRequests', function () { + afterEach(function () { + window.arcobj1 = undefined; + window.arcobj2 = undefined; + }); + + it('alters the bid request 1', function () { + setIAB({ + raw: { + images: [ + 'Religion & Spirituality', + 'Medical Health>Substance Abuse', + 'Religion & Spirituality>Astrology', + 'Medical Health', + 'Events & Attractions', + ], + }, + codes: { + images: ['IAB23-10', 'IAB7', 'IAB7-42', 'IAB15-1'], + }, + newcodes: { + images: ['150', '453', '311', '456', '286'], + }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Religion & Spirituality,Medical Health>Substance Abuse,Religion & Spirituality>Astrology,Medical Health,Events & Attractions' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB23_10', + 'IAB7', + 'IAB7_42', + 'IAB15_1', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '150' }, + { id: '453' }, + { id: '311' }, + { id: '456' }, + { id: '286' } + ]); + }); + }); + + it('alters the bid request 2', function () { + setIAB({ + raw: { text: ['Sports', 'Sports>Soccer'] }, + codes: { text: ['IAB17', 'IAB17-44'] }, + newcodes: { text: ['483', '533'] }, + }); + + var reqBidsConfigObj = {}; + reqBidsConfigObj.ortb2Fragments = {}; + reqBidsConfigObj.ortb2Fragments.global = {}; + arcspanSubmodule.getBidRequestData(reqBidsConfigObj, function () { + expect(reqBidsConfigObj.ortb2Fragments.global.site.name).to.equal( + 'arcspan' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal( + 'Sports,Sports>Soccer' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(6); + expect(reqBidsConfigObj.ortb2Fragments.global.site.cat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.sectioncat).to.eql([ + 'IAB17', + 'IAB17_44' + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.pagecat).to.eql([ + 'IAB17', + 'IAB17_44', + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + { id: '483' }, + { id: '533' } + ]); + }); + }); + }); +}); + +function getGoodConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 13, + }, + }; +} + +function getBadConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + notasilo: 1, + }, + }; +} + +function getTestConfig() { + return { + name: 'arcspan', + waitForIt: true, + params: { + silo: 'test', + }, + }; +} + +function setIAB(vjson) { + window.arcobj2 = {}; + window.arcobj2.cat = 0; + if (typeof vjson.codes != 'undefined') { + window.arcobj2.cat = 1; + if (typeof vjson.codes.images != 'undefined') { + vjson.codes.images.forEach(function f(e, i) { + vjson.codes.images[i] = e.replace('-', '_'); + }); + } + if (typeof vjson.codes.text != 'undefined') { + vjson.codes.text.forEach(function f(e, i) { + vjson.codes.text[i] = e.replace('-', '_'); + }); + } + window.arcobj2.sampled = 1; + window.arcobj1 = {}; + window.arcobj1.page_iab_codes = vjson.codes; + window.arcobj1.page_iab = vjson.raw; + window.arcobj1.page_iab_newcodes = vjson.newcodes; + } +} diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index 5ac44cb1db4..88016d1902c 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -125,7 +125,7 @@ describe('Adserver.Online bidding adapter', function () { expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); const query = parsedRequestUrl.search; - expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.pbjs).to.contain('$prebid.version$'); expect(query.zid).to.equal('1'); expect(request.data).to.exist; @@ -162,7 +162,7 @@ describe('Adserver.Online bidding adapter', function () { expect(parsedRequestUrl.pathname).to.equal('/prebid/bidder'); const query = parsedRequestUrl.search; - expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.pbjs).to.contain('$prebid.version$'); expect(query.zid).to.equal('2'); expect(request.data).to.not.be.empty; diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index c6001c3ba2e..ad096fd6526 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -216,4 +216,56 @@ describe('BeOp Bid Adapter tests', () => { expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad'); }); }); + + describe('Ensure keywords is always array of string', function () { + let bidRequests = []; + afterEach(function () { + bidRequests = []; + }); + + it('should work with keywords as an array', function () { + let bid = Object.assign({}, validBid); + bid.params.keywords = ['a', 'b']; + bidRequests.push(bid); + config.setConfig({ + currency: { adServerCurrency: 'USD' } + }); + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + const url = request.url; + expect(payload.kwds).to.exist; + expect(payload.kwds).to.include('a'); + expect(payload.kwds).to.include('b'); + }); + + it('should work with keywords as a string', function () { + let bid = Object.assign({}, validBid); + bid.params.keywords = 'list of keywords'; + bidRequests.push(bid); + config.setConfig({ + currency: { adServerCurrency: 'USD' } + }); + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + const url = request.url; + expect(payload.kwds).to.exist; + expect(payload.kwds).to.include('list of keywords'); + }); + + it('should work with keywords as a string containing a comma', function () { + let bid = Object.assign({}, validBid); + bid.params.keywords = 'list, of, keywords'; + bidRequests.push(bid); + config.setConfig({ + currency: { adServerCurrency: 'USD' } + }); + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + const url = request.url; + expect(payload.kwds).to.exist; + expect(payload.kwds).to.include('list'); + expect(payload.kwds).to.include('of'); + expect(payload.kwds).to.include('keywords'); + }) + }) }); diff --git a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js index 9e9d7dbe156..a72bb012fa1 100644 --- a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js +++ b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js @@ -297,7 +297,6 @@ describe('BidWatch Analytics', function () { expect(message.auctionEnd[0]).to.have.property('bidderRequests').and.to.have.lengthOf(1); expect(message.auctionEnd[0].bidderRequests[0]).to.have.property('gdprConsent'); expect(message.auctionEnd[0].bidderRequests[0].gdprConsent).not.to.have.property('vendorData'); - sinon.assert.callCount(bidwatchAnalytics.track, 4); }); it('test bidWon', function() { @@ -318,7 +317,6 @@ describe('BidWatch Analytics', function () { expect(message).not.to.have.property('ad'); expect(message).to.have.property('adId') expect(message).to.have.property('cpmIncrement').and.to.equal(27.4276); - sinon.assert.callCount(bidwatchAnalytics.track, 1); }); }); }); diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index b8994b86847..8e96bd76940 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -130,7 +130,7 @@ const getConfigCreative = () => { creativeId: '34567erty', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -145,7 +145,7 @@ const getConfigCreativeVideo = (isNoVast) => { requestId: '6a204ce130280d', width: 300, height: 250, - ttl: 3600, + ttl: 300, netRevenue: true, } } @@ -395,7 +395,7 @@ const testsInterpretResponse = [ mediaType: 'video', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(vastXml.replace(/\\"/g, '"')) }] @@ -421,7 +421,7 @@ const testsInterpretResponse = [ mediaType: 'banner', netRevenue: true, requestId: '2def0c5b2a7f6e', - ttl: 3600, + ttl: 300, width: 300 }] }, @@ -505,7 +505,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -535,7 +535,7 @@ const testsBuildBid = [ netRevenue: true, vastXml: getConfigCreativeVideo().vastXml, vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), - ttl: 3600, + ttl: 300, } }, { @@ -552,7 +552,7 @@ const testsBuildBid = [ height: 250, creativeId: getConfigCreative().creativeId, ad: getConfigBannerBid().creative.banner.adm, - ttl: 3600, + ttl: 300, netRevenue: true, } } diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js index 9354544ee5a..1ae73708d00 100644 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import * as utils from 'src/utils.js'; import { spec } from 'modules/brightcomBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; const URL = 'https://brightcombid.marphezis.com/hb'; @@ -52,7 +53,21 @@ describe('brightcomBidAdapter', function() { }, 'bidId': '5fb26ac22bde4', 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, }]; sandbox = sinon.sandbox.create(); @@ -167,6 +182,84 @@ describe('brightcomBidAdapter', function() { expect(data.user.ext.consent).to.equal(consentString); }); + it('sends us_privacy', function () { + const bidderRequest = { + uspConsent: '1YYY' + }; + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) + + expect(data.regs).to.not.equal(null); + expect(data.regs.ext).to.not.equal(null); + expect(data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('sends coppa', function () { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const data = JSON.parse(spec.buildRequests(bidRequests).data) + expect(data.regs).to.not.be.undefined; + expect(data.regs.coppa).to.equal(1); + }); + + it('sends schain', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data).to.not.be.undefined; + expect(data.source).to.not.be.undefined; + expect(data.source.ext).to.not.be.undefined; + expect(data.source.ext.schain).to.not.be.undefined; + expect(data.source.ext.schain.complete).to.equal(1); + expect(data.source.ext.schain.ver).to.equal('1.0'); + expect(data.source.ext.schain.nodes).to.not.be.undefined; + expect(data.source.ext.schain.nodes).to.lengthOf(1); + expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(data.source.ext.schain.nodes[0].hp).to.equal(1); + expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + + it('sends user eid parameters', function () { + bidRequests[0].userIdAsEids = [{ + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.eids).to.not.be.undefined; + expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + }); + + it('sends user id parameters', function () { + const userId = { + sharedid: { + id: '01*******', + third: '01E*******' + } + }; + + bidRequests[0].userId = userId; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.ids).is.deep.equal(userId); + }); + context('when element is fully in view', function() { it('returns 100', function() { Object.assign(element, { width: 600, height: 400 }); diff --git a/test/spec/modules/brightcomSSPBidAdapter_spec.js b/test/spec/modules/brightcomSSPBidAdapter_spec.js new file mode 100644 index 00000000000..6f35a7a290b --- /dev/null +++ b/test/spec/modules/brightcomSSPBidAdapter_spec.js @@ -0,0 +1,411 @@ +import { expect } from 'chai'; +import * as utils from 'src/utils.js'; +import { spec } from 'modules/brightcomSSPBidAdapter'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; + +const URL = 'https://rt.marphezis.com/hb'; + +describe('brightcomSSPBidAdapter', function() { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function() { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'bcmssp', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function() { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function() { + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'bcmssp', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + page: 'http://example.com/page.html', + domain: 'example.com', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + + it('sends us_privacy', function () { + const bidderRequest = { + uspConsent: '1YYY' + }; + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) + + expect(data.regs).to.not.equal(null); + expect(data.regs.ext).to.not.equal(null); + expect(data.regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('sends coppa', function () { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + + const data = JSON.parse(spec.buildRequests(bidRequests).data) + expect(data.regs).to.not.be.undefined; + expect(data.regs.coppa).to.equal(1); + }); + + it('sends schain', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data).to.not.be.undefined; + expect(data.source).to.not.be.undefined; + expect(data.source.ext).to.not.be.undefined; + expect(data.source.ext.schain).to.not.be.undefined; + expect(data.source.ext.schain.complete).to.equal(1); + expect(data.source.ext.schain.ver).to.equal('1.0'); + expect(data.source.ext.schain.nodes).to.not.be.undefined; + expect(data.source.ext.schain.nodes).to.lengthOf(1); + expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(data.source.ext.schain.nodes[0].hp).to.equal(1); + expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + + it('sends user eid parameters', function () { + bidRequests[0].userIdAsEids = [{ + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.eids).to.not.be.undefined; + expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + }); + + it('sends user id parameters', function () { + const userId = { + sharedid: { + id: '01*******', + third: '01E*******' + } + }; + + bidRequests[0].userId = userId; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.ids).is.deep.equal(userId); + }); + + context('when element is fully in view', function() { + it('returns 100', function() { + Object.assign(element, { width: 600, height: 400 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function() { + it('returns 0', function() { + Object.assign(element, { x: -300, y: 0, width: 207, height: 320 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function() { + it('returns percentage', function() { + Object.assign(element, { width: 800, height: 800 }); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function() { + it('try to use alternative values', function() { + Object.assign(element, { width: 0, height: 0 }); + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function() { + it('returns \'na\'', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function() { + it('returns 0', function() { + Object.assign(element, { width: 600, height: 400 }); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'] + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 60, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index 19483047827..75120aa7505 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -252,7 +252,8 @@ describe('browsi Real time data sub module', function () { }) it('should send event if type is correct', function () { sendPageviewEvent('PAGEVIEW') - + const pageViewEvent = new CustomEvent('browsi_pageview', {}); + window.dispatchEvent(pageViewEvent); const expectedCall = { vendor: 'browsi', type: 'pageview', diff --git a/test/spec/modules/captifyRtdProvider_spec.js b/test/spec/modules/captifyRtdProvider_spec.js new file mode 100644 index 00000000000..2e1052e000f --- /dev/null +++ b/test/spec/modules/captifyRtdProvider_spec.js @@ -0,0 +1,253 @@ +import {addSegmentData, captifySubmodule, getMatchingBidders, setCaptifyTargeting} from 'modules/captifyRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; +import {config} from 'src/config.js'; +import {deepAccess} from '../../../src/utils'; + +const responseHeader = {'Content-Type': 'application/json'}; +const defaultRequestUrl = 'https://live-classification.cpx.to/prebid-segments'; + +describe('captifyRtdProvider', function () { + describe('init function', function () { + it('successfully instantiates, when configured properly', function () { + const config = { + params: { + pubId: 123456, + bidders: ['appnexus'], + } + }; + expect(captifySubmodule.init(config, null)).to.equal(true); + }); + + it('return false on init, when config is invalid', function () { + const config = { + params: {} + }; + expect(captifySubmodule.init(config, null)).to.equal(false); + expect(captifySubmodule.init(null, null)).to.equal(false); + }); + + it('return false on init, when pubId is absent', function () { + const config = { + params: { + bidders: ['appnexus'], + } + }; + expect(captifySubmodule.init(config, null)).to.equal(false); + expect(captifySubmodule.init(null, null)).to.equal(false); + }); + + it('return false on init, when bidders is empty array', function () { + const config = { + params: { + bidders: [], + pubId: 123, + } + }; + expect(captifySubmodule.init(config, null)).to.equal(false); + expect(captifySubmodule.init(null, null)).to.equal(false); + }); + }); + + describe('addSegmentData function', function () { + it('adds segment data', function () { + config.resetConfig(); + + let data = { + xandr: [111111, 222222], + }; + + addSegmentData(['appnexus'], data); + expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.eql(data['xandr']); + }); + }); + + describe('getMatchingBidders function', function () { + it('returns only bidders that used within adUnits', function () { + const moduleConfig = { + params: { + pubId: 123, + bidders: ['appnexus', 'pubmatic'], + } + }; + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }] + }] + }; + + let matchedBidders = getMatchingBidders(moduleConfig, reqBidsConfigObj); + expect(matchedBidders).to.eql(['appnexus']); + }); + + it('return empty result, when there are no bidders configured for adUnits', function () { + const moduleConfig = { + params: { + pubId: 123, + bidders: ['pubmatic'], + } + }; + let reqBidsConfigObj = { + adUnits: [{ + bids: [] + }] + }; + expect(getMatchingBidders(moduleConfig, reqBidsConfigObj)).to.be.empty; + }); + + it('return empty result, when there are no bidders matched', function () { + const moduleConfig = { + params: { + pubId: 123, + bidders: ['pubmatic'], + } + }; + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + params: { + bidder: 'appnexus', + placementId: 13144370, + } + }] + }] + }; + expect(getMatchingBidders(moduleConfig, reqBidsConfigObj)).to.be.empty; + }); + + it('return empty result, when there are no adUnits with bidders', function () { + const moduleConfig = { + params: { + pubId: 123, + bidders: ['pubmatic'], + } + }; + let reqBidsConfigObj = { + adUnits: [{ + bids: [{ + params: { + placementId: 13144370, + } + }] + }] + }; + expect(getMatchingBidders(moduleConfig, reqBidsConfigObj)).to.be.empty; + }); + }); + + describe('integration test with mock live-classification response', function () { + const moduleConfig = { + params: { + pubId: 123456, + bidders: ['appnexus'], + } + }; + + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'appnexus', + params: { + placementId: 13144370 + } + }, { + bidder: 'other' + }] + }] + }; + + const expectedUrlParam = 'http://localhost:9876/context.html'; + + it('gets data from async request and adds segment data', function () { + config.resetConfig(); + let data = {xandr: [111111, 222222]}; + const callbackSpy = sinon.spy(); + setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); + let request = server.requests[0]; + let requestBody = JSON.parse(server.requests[0].requestBody); + expect(request.url).to.be.eq(defaultRequestUrl); + expect(requestBody['pubId']).to.eq(moduleConfig.params.pubId); + expect(requestBody['url']).to.eq(expectedUrlParam); + request.respond(200, responseHeader, JSON.stringify(data)); + expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.eql(data['xandr']); + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('do not send classification request, if no matching adUnits on page', function () { + config.resetConfig(); + let reqBidsConfigObj = { + adUnits: [{ + bids: [ + {bidder: 'pubmatic'} + ] + }] + }; + const callbackSpy = sinon.spy(); + setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); + expect(server.requests).to.be.empty; + }); + + it('gets data from async request and adds segment data, using URL from config', function () { + config.resetConfig(); + let data = {xandr: [111111, 222222]}; + const callbackSpy = sinon.spy(); + const testUrl = 'http://my-test-server.com/path'; + const conf = { + params: { + url: testUrl, + pubId: 123456, + bidders: ['appnexus'], + } + }; + setCaptifyTargeting(reqBidsConfigObj, callbackSpy, conf, {}); + let request = server.requests[0]; + let requestBody = JSON.parse(server.requests[0].requestBody); + expect(request.url).to.be.eq(testUrl); + expect(requestBody['pubId']).to.eq(conf.params.pubId); + expect(requestBody['url']).to.eq(expectedUrlParam); + request.respond(200, responseHeader, JSON.stringify(data)); + expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.eql(data['xandr']); + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('do not set anything, in case server responded with 202', function () { + config.resetConfig(); + const callbackSpy = sinon.spy(); + setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); + let request = server.requests[0]; + let requestBody = JSON.parse(server.requests[0].requestBody); + expect(request.url).to.be.eq(defaultRequestUrl); + expect(requestBody['pubId']).to.eq(moduleConfig.params.pubId); + expect(requestBody['url']).to.eq(expectedUrlParam); + request.respond(202, responseHeader, ''); + expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.be.undefined; + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('do not set anything, in case server responded with error', function () { + config.resetConfig(); + const callbackSpy = sinon.spy(); + setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); + let request = server.requests[0]; + expect(request.url).to.be.eq(defaultRequestUrl); + request.respond(500, null, ''); + expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.be.undefined; + expect(callbackSpy.calledOnce).to.be.true; + }); + + it('do not set anything, in case request error', function () { + config.resetConfig(); + const callbackSpy = sinon.spy(); + setCaptifyTargeting(reqBidsConfigObj, callbackSpy, moduleConfig, {}); + let request = server.requests[0]; + expect(request.url).to.be.eq(defaultRequestUrl); + request.abort('test error'); + expect(deepAccess(config.getConfig(), 'appnexusAuctionKeywords.captify_segments')).to.be.undefined; + expect(callbackSpy.calledOnce).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/chtnwBidAdapter_spec.js b/test/spec/modules/chtnwBidAdapter_spec.js new file mode 100644 index 00000000000..3875de6c4fa --- /dev/null +++ b/test/spec/modules/chtnwBidAdapter_spec.js @@ -0,0 +1,105 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/chtnwBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('ChtnwAdapter', function () { + describe('isBidRequestValid', function () { + const bid = { + code: 'adunit-code', + bidder: 'chtnw', + params: { + placementId: '38EL412LO82XR9O6' + }, + sizes: [ + [300, 250] + ], + }; + + it('should return true when required params found', function () { + const bidRequest = utils.deepClone(bid); + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false when required params are missing', function () { + const bidRequest = utils.deepClone(bid); + delete bidRequest.params; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [{ + code: 'adunit-code', + bidder: 'chtnw', + params: { + placementId: '38EL412LO82XR9O6' + }, + sizes: [ + [300, 250] + ], + }]; + + const request = spec.buildRequests(bidRequests); + + it('Returns POST method', function () { + expect(request.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + let data = request.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('bids'); + expect(data).to.have.property('uuid'); + expect(data).to.have.property('device'); + expect(data).to.have.property('site'); + }); + }); + + describe('interpretResponse', function () { + let responseBody = [{ + 'requestId': 'test', + 'cpm': 0.5, + 'currency': 'USD', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'ttl': 60, + 'creativeId': 'AD', + 'ad': '

AD

', + 'mediaType': 'banner', + 'meta': { + 'advertiserDomains': [ + 'www.example.com' + ] + } + }]; + + it('handles empty bid response', function () { + let response = { + body: responseBody + }; + let result = spec.interpretResponse(response); + expect(result.length).to.not.equal(0); + expect(result[0].meta.advertiserDomains).to.be.an('array'); + }); + + it('handles empty bid response', function () { + let response = { + body: [] + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + it('should register type is image', function () { + const syncOptions = { + 'pixelEnabled': 'true' + } + let userSync = spec.getUserSyncs(syncOptions); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.have.string('ssp'); + }); + }); +}); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 8b06868390a..adb9137d892 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -56,6 +56,93 @@ describe('ColossussspAdapter', function () { referer: 'http://www.example.com', reachedTop: true, }, + ortb2: { + app: { + name: 'myappname', + keywords: 'power tools, drills', + content: { + data: [ + { + name: 'www.dataprovider1.com', + ext: { + segtax: 6 + }, + segment: [ + { + id: '687' + }, + { + id: '123' + } + ] + }, + { + name: 'www.dataprovider1.com', + ext: { + segtax: 7 + }, + segment: [ + { + id: '456' + }, + { + id: '789' + } + ] + } + ] + } + }, + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'], + page: 'https://page.example.com/here.html', + ref: 'https://ref.example.com', + keywords: 'power tools, drills', + search: 'drill', + content: { + userrating: '4', + data: [{ + name: 'www.dataprovider1.com', + ext: { + segtax: 7, + cids: ['iris_c73g5jq96mwso4d8'] + }, + segment: [ + { id: '687' }, + { id: '123' } + ] + }] + }, + ext: { + data: { + pageType: 'article', + category: 'repair' + } + } + }, + user: { + yob: 1985, + gender: 'm', + keywords: 'a,b', + data: [{ + name: 'dataprovider.com', + ext: { segtax: 4 }, + segment: [ + { id: '1' } + ] + }], + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + }, bids: [bid] } @@ -91,7 +178,7 @@ describe('ColossussspAdapter', function () { it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require', 'userObj', 'siteObj', 'appObj'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); @@ -132,7 +219,7 @@ describe('ColossussspAdapter', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr_consent', 'gdpr_require', 'userObj', 'siteObj', 'appObj'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); @@ -299,8 +386,8 @@ describe('ColossussspAdapter', function () { expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('hms.gif'); - expect(userSync[0].url).to.be.equal('https://sync.colossusssp.com/hms.gif?pbjs=1&gdpr=0&gdpr_consent=xxx&ccpa_consent=1YN-&coppa=0'); + expect(userSync[0].type).to.be.equal('image'); + expect(userSync[0].url).to.be.equal('https://sync.colossusssp.com/image?pbjs=1&gdpr=0&gdpr_consent=xxx&ccpa_consent=1YN-&coppa=0'); }); }); }); diff --git a/test/spec/modules/concertAnalyticsAdapter_spec.js b/test/spec/modules/concertAnalyticsAdapter_spec.js index d130aea6043..1df73ae04fe 100644 --- a/test/spec/modules/concertAnalyticsAdapter_spec.js +++ b/test/spec/modules/concertAnalyticsAdapter_spec.js @@ -1,5 +1,6 @@ import concertAnalytics from 'modules/concertAnalyticsAdapter.js'; import { expect } from 'chai'; +import {expectEvents} from '../../helpers/analytics.js'; const sinon = require('sinon'); let adapterManager = require('src/adapterManager').default; let events = require('src/events'); @@ -46,10 +47,7 @@ describe('ConcertAnalyticsAdapter', function() { it('should catch all events', function() { sandbox.spy(concertAnalytics, 'track'); - - fireBidEvents(events); - // 5 Concert events + 1 Clean.io event - sandbox.assert.callCount(concertAnalytics.track, 6); + expectEvents().to.beTrackedBy(concertAnalytics.track); }); it('should report data for BID_RESPONSE, BID_WON events', function() { diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 2f9eda0ca7c..00626472ecb 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -1,22 +1,44 @@ import { expect } from 'chai'; import sinon from 'sinon'; -import { spec } from 'modules/concertBidAdapter.js'; -import { getStorageManager } from '../../../src/storageManager.js' +import { spec, storage } from 'modules/concertBidAdapter.js'; +import { hook } from 'src/hook.js'; describe('ConcertAdapter', function () { let bidRequests; let bidRequest; let bidResponse; + let element; + let sandbox; - afterEach(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; + before(function () { + hook.ready(); }); + beforeEach(function () { + element = { + x: 0, + y: 0, + width: 0, + height: 0, + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + $$PREBID_GLOBAL$$.bidderSettings = { concert: { storageAllowed: true } }; + bidRequests = [ { bidder: 'concert', @@ -56,6 +78,14 @@ describe('ConcertAdapter', function () { ] } } + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('desktop_leaderboard_variable').returns(element) + }); + + afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); }); describe('spec.isBidRequestValid', function() { @@ -93,7 +123,6 @@ describe('ConcertAdapter', function () { }); it('should not generate uid if the user has opted out', function() { - const storage = getStorageManager(); storage.setDataInLocalStorage('c_nap', 'true'); const request = spec.buildRequests(bidRequests, bidRequest); const payload = JSON.parse(request.data); @@ -102,7 +131,6 @@ describe('ConcertAdapter', function () { }); it('should generate uid if the user has not opted out', function() { - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, bidRequest); const payload = JSON.parse(request.data); @@ -110,15 +138,59 @@ describe('ConcertAdapter', function () { expect(payload.meta.uid).to.not.equal(false); }); - it('should grab uid from local storage if it exists', function() { - const storage = getStorageManager(); - storage.setDataInLocalStorage('c_uid', 'foo'); + it('should use sharedid if it exists', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { + ...bidRequest, + userId: { + _sharedid: { + id: '123abc' + } + } + }); + const payload = JSON.parse(request.data); + expect(payload.meta.uid).to.equal('123abc'); + }) + + it('should grab uid from local storage if it exists and sharedid does not', function() { + storage.setDataInLocalStorage('vmconcert_uid', 'foo'); storage.removeDataFromLocalStorage('c_nap'); const request = spec.buildRequests(bidRequests, bidRequest); const payload = JSON.parse(request.data); expect(payload.meta.uid).to.equal('foo'); }); + + it('should add uid2 to eids list if available', function() { + bidRequests[0].userId = { uid2: { id: 'uid123' } } + + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + const meta = payload.meta + + expect(meta.eids.length).to.equal(1); + expect(meta.eids[0].uids[0].id).to.equal('uid123') + expect(meta.eids[0].uids[0].atype).to.equal(3) + }) + + it('should return empty eids list if none are available', function() { + bidRequests[0].userId = { testId: { id: 'uid123' } } + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + const meta = payload.meta + + expect(meta.eids.length).to.equal(0); + }); + + it('should return x/y offset coordiantes when element is present', function() { + Object.assign(element, { x: 100, y: 0, width: 400, height: 400 }) + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + const slot = payload.slots[0]; + + expect(slot.offsetCoordinates.x).to.equal(100) + expect(slot.offsetCoordinates.y).to.equal(0) + }) }); describe('spec.interpretResponse', function() { @@ -155,7 +227,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.setDataInLocalStorage('c_nap', 'true'); const sync = spec.getUserSyncs(opts, [], bidRequest.gdprConsent, bidRequest.uspConsent); @@ -166,7 +237,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { @@ -181,7 +251,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { @@ -196,7 +265,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { @@ -212,7 +280,6 @@ describe('ConcertAdapter', function () { const opts = { iframeEnabled: true }; - const storage = getStorageManager(); storage.removeDataFromLocalStorage('c_nap'); bidRequest.gdprConsent = { diff --git a/test/spec/modules/confiantRtdProvider_spec.js b/test/spec/modules/confiantRtdProvider_spec.js new file mode 100644 index 00000000000..987aca2e020 --- /dev/null +++ b/test/spec/modules/confiantRtdProvider_spec.js @@ -0,0 +1,107 @@ +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js' +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; + +import confiantModule from '../../../modules/confiantRtdProvider.js'; + +const { + injectConfigScript, + setupPage, + subscribeToConfiantCommFrame, + registerConfiantSubmodule +} = confiantModule; + +describe('Confiant RTD module', function () { + describe('setupPage()', function() { + it('should return false if propertId is not present in config', function() { + expect(setupPage({})).to.be.false; + expect(setupPage({ params: {} })).to.be.false; + expect(setupPage({ params: { propertyId: '' } })).to.be.false; + }); + + it('should return true if expected parameters are present', function() { + expect(setupPage({ params: { propertyId: 'clientId' } })).to.be.true; + }); + }); + + describe('Module initialization', function() { + let insertElementStub; + beforeEach(function() { + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function() { + utils.insertElement.restore(); + }); + + it('should subscribe to rovided Window object', function () { + const mockWindow = { addEventListener: sinon.spy() }; + + subscribeToConfiantCommFrame(mockWindow); + + sinon.assert.calledOnce(mockWindow.addEventListener); + }); + + it('should fire BillableEvent as a result for message in comm window', function() { + let listenerCallback; + const mockWindow = { addEventListener: (a, cb) => (listenerCallback = cb) }; + let billableEventsCounter = 0; + + events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { + if (e.vendor === 'confiant') { + billableEventsCounter++; + expect(e.type).to.equal('impression'); + expect(e.billingId).to.be.a('number'); + expect(e.auctionId).to.equal('auctionId'); + expect(e.transactionId).to.equal('transactionId'); + } + }); + + subscribeToConfiantCommFrame(mockWindow); + listenerCallback({ + data: { + auctionId: 'auctionId', + transactionId: 'transactionId' + } + }); + listenerCallback({ + data: { + auctionId: 'auctionId', + transactionId: 'transactionId' + } + }); + + expect(billableEventsCounter).to.equal(2); + }); + }); + + describe('Sumbodule execution', function() { + let submoduleStub; + let insertElementStub; + beforeEach(function () { + submoduleStub = sinon.stub(hook, 'submodule'); + insertElementStub = sinon.stub(utils, 'insertElement'); + }); + afterEach(function () { + utils.insertElement.restore(); + submoduleStub.restore(); + }); + + function initModule() { + registerConfiantSubmodule(); + + expect(submoduleStub.calledOnceWith('realTimeData')).to.equal(true); + + const registeredSubmoduleDefinition = submoduleStub.getCall(0).args[1]; + expect(registeredSubmoduleDefinition).to.be.an('object'); + expect(registeredSubmoduleDefinition).to.have.own.property('name', 'confiant'); + expect(registeredSubmoduleDefinition).to.have.own.property('init').that.is.a('function'); + + return registeredSubmoduleDefinition; + } + + it('should register Confiant submodule', function () { + initModule(); + }); + }); +}); diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 41135a74515..52639c39baf 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -4,6 +4,7 @@ import {connectIdSubmodule} from 'modules/connectIdSystem.js'; describe('Yahoo ConnectID Submodule', () => { const HASHED_EMAIL = '6bda6f2fa268bf0438b5423a9861a2cedaa5dec163c03f743cfe05c08a8397b2'; + const PUBLISHER_USER_ID = '975484817'; const PIXEL_ID = '1234'; const PROD_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PIXEL_ID}/fed`; const OVERRIDE_ENDPOINT = 'https://foo/bar'; @@ -30,7 +31,11 @@ describe('Yahoo ConnectID Submodule', () => { gdprApplies: 1, consentString: 'GDPR_CONSENT_STRING' }, - uspConsent: 'USP_CONSENT_STRING' + uspConsent: 'USP_CONSENT_STRING', + gppConsent: { + gppString: 'header~section6~section7', + applicableSections: [6, 7] + } }; }); @@ -48,47 +53,165 @@ describe('Yahoo ConnectID Submodule', () => { return result; } - it('returns undefined if he and pixelId params are not passed', () => { + it('returns undefined if he, pixelId and puid params are not passed', () => { expect(invokeGetIdAPI({}, consentData)).to.be.undefined; expect(ajaxStub.callCount).to.equal(0); }); it('returns undefined if the pixelId param is not passed', () => { expect(invokeGetIdAPI({ - he: HASHED_EMAIL + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID }, consentData)).to.be.undefined; expect(ajaxStub.callCount).to.equal(0); }); - it('returns undefined if the he param is not passed', () => { + it('returns undefined if the pixelId param is passed, but the he and puid param are not passed', () => { expect(invokeGetIdAPI({ pixelId: PIXEL_ID }, consentData)).to.be.undefined; expect(ajaxStub.callCount).to.equal(0); }); - it('returns an object with the callback function if the correct params are passed', () => { + it('returns an object with the callback function if the endpoint override param and the he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the endpoint override param and the puid and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, + endpoint: OVERRIDE_ENDPOINT + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and he params are passed', () => { + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId and puid params are passed', () => { + let result = invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + }); + + it('returns an object with the callback function if the pixelId, he and puid params are passed', () => { let result = invokeGetIdAPI({ he: HASHED_EMAIL, + puid: PUBLISHER_USER_ID, pixelId: PIXEL_ID }, consentData); expect(result).to.be.an('object').that.has.all.keys('callback'); expect(result.callback).to.be.a('function'); }); - it('Makes an ajax GET request to the production API endpoint with query params', () => { + it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { + localStorage.setItem('connectIdOptOut', '1'); + expect(invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData)).to.be.undefined; + localStorage.removeItem('connectIdOptOut'); + }); + + it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { + localStorage.setItem('connectIdOptOut', 'true'); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + expect(result).to.be.an('object').that.has.all.keys('callback'); + expect(result.callback).to.be.a('function'); + localStorage.removeItem('connectIdOptOut'); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { + invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId and puid query params', () => { invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID + }, consentData); + + const expectedParams = { + puid: PUBLISHER_USER_ID, + pixelId: PIXEL_ID, + '1p': '0', + gdpr: '1', + gdpr_consent: consentData.gdpr.consentString, + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' + }; + const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); + + expect(ajaxStub.firstCall.args[0].indexOf(`${PROD_ENDPOINT}?`)).to.equal(0); + expect(requestQueryParams).to.deep.equal(expectedParams); + expect(ajaxStub.firstCall.args[3]).to.deep.equal({method: 'GET', withCredentials: true}); + }); + + it('Makes an ajax GET request to the production API endpoint with pixelId, puid and he query params', () => { + invokeGetIdAPI({ + puid: PUBLISHER_USER_ID, he: HASHED_EMAIL, pixelId: PIXEL_ID }, consentData); const expectedParams = { + puid: PUBLISHER_USER_ID, he: HASHED_EMAIL, pixelId: PIXEL_ID, '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' }; const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -108,7 +231,9 @@ describe('Yahoo ConnectID Submodule', () => { '1p': '0', gdpr: '1', gdpr_consent: consentData.gdpr.consentString, - us_privacy: consentData.uspConsent + us_privacy: consentData.uspConsent, + gpp: consentData.gppConsent.gppString, + gpp_sid: '6%2C7' }; const requestQueryParams = parseQS(ajaxStub.firstCall.args[0].split('?')[1]); @@ -165,6 +290,16 @@ describe('Yahoo ConnectID Submodule', () => { }); describe('decode()', () => { + let userHasOptedOutStub; + beforeEach(() => { + userHasOptedOutStub = sinon.stub(connectIdSubmodule, 'userHasOptedOut'); + userHasOptedOutStub.returns(false); + }); + + afterEach(() => { + userHasOptedOutStub.restore() + }); + const VALID_API_RESPONSES = [{ key: 'connectid', expected: '4567', @@ -185,5 +320,72 @@ describe('Yahoo ConnectID Submodule', () => { expect(connectIdSubmodule.decode(response)).to.be.undefined; }); }); + + it('should return undefined if user has utilised the easy opt-out mechanism', () => { + userHasOptedOutStub.returns(true); + expect(connectIdSubmodule.decode(VALID_API_RESPONSES[0].payload)).to.be.undefined; + }) + }); + + describe('getAjaxFn()', () => { + it('should return a function', () => { + expect(connectIdSubmodule.getAjaxFn()).to.be.a('function'); + }); + }); + + describe('isEUConsentRequired()', () => { + it('should return a function', () => { + expect(connectIdSubmodule.isEUConsentRequired).to.be.a('function'); + }); + + it('should be false if consent data is empty', () => { + expect(connectIdSubmodule.isEUConsentRequired({})).to.be.false; + }); + + it('should be false if consent data.gdpr object is empty', () => { + expect(connectIdSubmodule.isEUConsentRequired({ + gdpr: {} + })).to.be.false; + }); + + it('should return false if consent data.gdpr.applies is false', () => { + expect(connectIdSubmodule.isEUConsentRequired({ + gdpr: { + gdprApplies: false + } + })).to.be.false; + }); + + it('should return true if consent data.gdpr.applies is true', () => { + expect(connectIdSubmodule.isEUConsentRequired({ + gdpr: { + gdprApplies: true + } + })).to.be.true; + }); + }); + + describe('userHasOptedOut()', () => { + afterEach(() => { + localStorage.removeItem('connectIdOptOut'); + }); + + it('should return a function', () => { + expect(connectIdSubmodule.userHasOptedOut).to.be.a('function'); + }); + + it('should return false when local storage key has not been set function', () => { + expect(connectIdSubmodule.userHasOptedOut()).to.be.false; + }); + + it('should return true when local storage key has been set to "1"', () => { + localStorage.setItem('connectIdOptOut', '1'); + expect(connectIdSubmodule.userHasOptedOut()).to.be.true; + }); + + it('should return false when local storage key has not been set to "1"', () => { + localStorage.setItem('connectIdOptOut', 'hello'); + expect(connectIdSubmodule.userHasOptedOut()).to.be.false; + }); }); }); diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js new file mode 100644 index 00000000000..1170f418caf --- /dev/null +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -0,0 +1,577 @@ +import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData } from 'modules/consentManagementGpp.js'; +import { gppDataHandler } from 'src/adapterManager.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; +import 'src/prebid.js'; + +let expect = require('chai').expect; + +describe('consentManagementGpp', function () { + describe('setConsentConfig tests:', function () { + describe('empty setConsentConfig value', function () { + beforeEach(function () { + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + it('should use system default values', function () { + setConsentConfig({ + gpp: {} + }); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + sinon.assert.callCount(utils.logInfo, 3); + }); + + it('should exit consent manager if config is not an object', function () { + setConsentConfig(''); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); + + it('should exit consentManagement module if config is "undefined"', function () { + setConsentConfig(undefined); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); + + it('should not produce any consent metadata', function () { + setConsentConfig(undefined) + let consentMetadata = gppDataHandler.getConsentMeta(); + expect(consentMetadata).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }) + + it('should immediately look up consent data', () => { + setConsentConfig({ + gpp: { + cmpApi: 'invalid' + } + }); + expect(gppDataHandler.ready).to.be.true; + }) + }); + + describe('valid setConsentConfig value', function () { + afterEach(function () { + config.resetConfig(); + }); + + it('results in all user settings overriding system defaults', function () { + let allConfig = { + gpp: { + cmpApi: 'iab', + timeout: 7500 + } + }; + + setConsentConfig(allConfig); + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(7500); + }); + + it('should recognize config.gpp, with default cmpApi and timeout', function () { + setConsentConfig({ + gpp: {} + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + }); + + it('should enable gppDataHandler', () => { + setConsentConfig({ + gpp: {} + }); + expect(gppDataHandler.enabled).to.be.true; + }); + }); + + describe('static consent string setConsentConfig value', () => { + afterEach(() => { + config.resetConfig(); + }); + + it('results in user settings overriding system defaults for v2 spec', () => { + let staticConfig = { + gpp: { + cmpApi: 'static', + timeout: 7500, + consentData: { + applicableSections: [7], + gppString: 'ABCDEFG1234', + gppVersion: 1, + sectionId: 3, + sectionList: [] + } + } + }; + + setConsentConfig(staticConfig); + expect(userCMP).to.be.equal('static'); + expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used + const consent = gppDataHandler.getConsentData(); + expect(consent.gppString).to.eql(staticConfig.gpp.consentData.gppString); + expect(consent.fullGppData).to.eql(staticConfig.gpp.consentData); + expect(staticConsentData).to.be.equal(staticConfig.gpp.consentData); + }); + }); + }); + + describe('requestBidsHook tests:', function () { + let goodConfig = { + gpp: { + cmpApi: 'iab', + timeout: 7500, + } + }; + + const staticConfig = { + gpp: { + cmpApi: 'static', + timeout: 7500, + consentData: { + gppString: 'abc12345', + applicableSections: [7] + } + } + } + + let didHookReturn; + + beforeEach(resetConsentData); + after(resetConsentData); + + describe('error checks:', function () { + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logWarn'); + sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + utils.logWarn.restore(); + utils.logError.restore(); + config.resetConfig(); + }); + + it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', function () { + let badCMPConfig = { + gpp: { + cmpApi: 'bad' + } + }; + setConsentConfig(badCMPConfig); + expect(userCMP).to.be.equal(badCMPConfig.gpp.cmpApi); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logWarn); + expect(didHookReturn).to.be.true; + expect(consent).to.be.null; + }); + + it('should call gppDataHandler.setConsentData() when unknown CMP api is used', () => { + setConsentConfig({ + gpp: { + cmpApi: 'invalid' + } + }); + let hookRan = false; + requestBidsHook(() => { + hookRan = true; + }, {}); + expect(hookRan).to.be.true; + expect(gppDataHandler.ready).to.be.true; + }) + + it('should throw proper errors when CMP is not found', function () { + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + // throw 2 errors; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config) + sinon.assert.calledTwice(utils.logError); + expect(didHookReturn).to.be.false; + expect(consent).to.be.null; + expect(gppDataHandler.ready).to.be.true; + }); + + it('should not trip when adUnits have no size', () => { + setConsentConfig(staticConfig); + let ran = false; + requestBidsHook(() => { + ran = true; + }, { + adUnits: [{ + code: 'test', + mediaTypes: { + video: {} + } + }] + }); + return gppDataHandler.promise.then(() => { + expect(ran).to.be.true; + }); + }); + + it('should continue the auction immediately, without consent data, if timeout is 0', (done) => { + setConsentConfig({ + gpp: { + cmpApi: 'iab', + timeout: 0 + } + }); + window.__gpp = function () {}; + try { + requestBidsHook(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([]); + expect(consent.gppString).to.be.undefined; + done(); + }, {}) + } finally { + delete window.__gpp; + } + }); + }); + + describe('already known consentData:', function () { + let cmpStub = sinon.stub(); + + function mockCMP(cmpResponse) { + return function (...args) { + if (args[0] === 'addEventListener') { + args[1](({ + eventName: 'sectionChange' + })); + } else if (args[0] === 'getGPPData') { + return cmpResponse; + } + } + } + + beforeEach(function () { + didHookReturn = false; + window.__gpp = function () {}; + }); + + afterEach(function () { + config.resetConfig(); + cmpStub.restore(); + delete window.__gpp; + resetConsentData(); + }); + + it('should bypass CMP and simply use previously stored consentData', function () { + let testConsentData = { + applicableSections: [7], + gppString: 'xyz', + }; + + cmpStub = sinon.stub(window, '__gpp').callsFake(mockCMP(testConsentData)); + setConsentConfig(goodConfig); + requestBidsHook(() => {}, {}); + cmpStub.reset(); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + + expect(didHookReturn).to.be.true; + expect(consent.gppString).to.equal(testConsentData.gppString); + expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); + sinon.assert.notCalled(cmpStub); + }); + }); + + describe('iframe tests', function () { + let cmpPostMessageCb = () => {}; + let stringifyResponse; + + function createIFrameMarker(frameName) { + let ifr = document.createElement('iframe'); + ifr.width = 0; + ifr.height = 0; + ifr.name = frameName; + document.body.appendChild(ifr); + return ifr; + } + + function creatCmpMessageHandler(prefix, returnEvtValue, returnGPPValue) { + return function (event) { + if (event && event.data) { + let data = event.data; + if (data[`${prefix}Call`]) { + let callId = data[`${prefix}Call`].callId; + let response; + if (data[`${prefix}Call`].command === 'addEventListener') { + response = { + [`${prefix}Return`]: { + callId, + returnValue: returnEvtValue, + success: true + } + } + } else if (data[`${prefix}Call`].command === 'getGPPData') { + response = { + [`${prefix}Return`]: { + callId, + returnValue: returnGPPValue, + success: true + } + } + } + event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + } + } + } + } + + function testIFramedPage(testName, messageFormatString, tarConsentString, tarSections) { + it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { + stringifyResponse = messageFormatString; + setConsentConfig(goodConfig); + requestBidsHook(() => { + let consent = gppDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(consent.gppString).to.equal(tarConsentString); + expect(consent.applicableSections).to.deep.equal(tarSections); + done(); + }, {}); + }); + } + + beforeEach(function () { + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logError.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + describe('v2 CMP workflow for iframe pages:', function () { + stringifyResponse = false; + let ifr2 = null; + + beforeEach(function () { + ifr2 = createIFrameMarker('__gppLocator'); + cmpPostMessageCb = creatCmpMessageHandler('__gpp', { + eventName: 'sectionChange' + }, { + gppString: 'abc12345234', + applicableSections: [7] + }); + window.addEventListener('message', cmpPostMessageCb, false); + }); + + afterEach(function () { + delete window.__gpp; // deletes the local copy made by the postMessage CMP call function + document.body.removeChild(ifr2); + window.removeEventListener('message', cmpPostMessageCb); + }); + + testIFramedPage('with/JSON response', false, 'abc12345234', [7]); + testIFramedPage('with/String response', true, 'abc12345234', [7]); + }); + }); + + describe('direct calls to CMP API tests', function () { + let cmpStub = sinon.stub(); + + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + config.resetConfig(); + cmpStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + resetConsentData(); + }); + + describe('v2 CMP workflow for normal pages:', function () { + beforeEach(function () { + window.__gpp = function () {}; + }); + + afterEach(function () { + delete window.__gpp; + }); + + it('performs lookup check and stores consentData for a valid existing user', function () { + let testConsentData = { + gppString: 'abc12345234', + applicableSections: [7] + }; + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consent = gppDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logError); + expect(didHookReturn).to.be.true; + expect(consent.gppString).to.equal(testConsentData.gppString); + expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); + }); + + it('produces gdpr metadata', function () { + let testConsentData = { + gppString: 'abc12345234', + applicableSections: [7] + }; + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + let consentMeta = gppDataHandler.getConsentMeta(); + sinon.assert.notCalled(utils.logError); + expect(consentMeta.generatedAt).to.be.above(1644367751709); + }); + + it('throws an error when processCmpData check fails + does not call requestBids callback', function () { + let testConsentData = {}; + let bidsBackHandlerReturn = false; + + cmpStub = sinon.stub(window, '__gpp').callsFake((...args) => { + if (args[0] === 'addEventListener') { + args[1]({ + eventName: 'sectionChange' + }); + } else if (args[0] === 'getGPPData') { + return testConsentData; + } + }); + + setConsentConfig(goodConfig); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + [utils.logWarn, utils.logError].forEach((stub) => stub.reset()); + + requestBidsHook(() => { + didHookReturn = true; + }, { + bidsBackHandler: () => bidsBackHandlerReturn = true + }); + let consent = gppDataHandler.getConsentData(); + + sinon.assert.calledOnce(utils.logError); + sinon.assert.notCalled(utils.logWarn); + expect(didHookReturn).to.be.false; + expect(bidsBackHandlerReturn).to.be.true; + expect(consent).to.be.null; + expect(gppDataHandler.ready).to.be.true; + }); + + describe('when proper consent is not available', () => { + let gppStub; + + function runAuction() { + setConsentConfig({ + gpp: { + cmpApi: 'iab', + timeout: 10, + } + }); + return new Promise((resolve, reject) => { + requestBidsHook(() => { + didHookReturn = true; + }, {}); + setTimeout(() => didHookReturn ? resolve() : reject(new Error('Auction did not run')), 20); + }) + } + + function mockGppCmp(gppdata) { + gppStub.callsFake((api, cb) => { + if (api === 'addEventListener') { + // eslint-disable-next-line standard/no-callback-literal + cb({ + pingData: { + cmpStatus: 'loaded' + } + }, true); + } + if (api === 'getGPPData') { + return gppdata; + } + }); + } + + beforeEach(() => { + gppStub = sinon.stub(window, '__gpp'); + }); + + afterEach(() => { + gppStub.restore(); + }) + + it('should continue auction with null consent when CMP is unresponsive', () => { + return runAuction().then(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([]); + expect(consent.gppString).to.be.undefined; + expect(gppDataHandler.ready).to.be.true; + }); + }); + + it('should use consent provided by events other than sectionChange', () => { + mockGppCmp({ + gppString: 'mock-consent-string', + applicableSections: [7] + }); + return runAuction().then(() => { + const consent = gppDataHandler.getConsentData(); + expect(consent.applicableSections).to.deep.equal([7]); + expect(consent.gppString).to.equal('mock-consent-string'); + expect(gppDataHandler.ready).to.be.true; + }); + }); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index 39aeee4470b..e98486754ab 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -8,8 +8,9 @@ import { } from 'modules/consentManagementUsp.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; -import {uspDataHandler} from 'src/adapterManager.js'; +import adapterManager, {gdprDataHandler, uspDataHandler} from 'src/adapterManager.js'; import 'src/prebid.js'; +import {defer} from '../../../src/utils/promise.js'; let expect = require('chai').expect; @@ -23,6 +24,16 @@ function createIFrameMarker() { } describe('consentManagement', function () { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(adapterManager, 'callDataDeletionRequest'); + }); + + afterEach(() => { + sandbox.restore(); + }); + it('should enable itself on requestBids using default values', (done) => { requestBidsHook(() => { expect(uspDataHandler.enabled).to.be.true; @@ -301,8 +312,11 @@ describe('consentManagement', function () { describe('USPAPI workflow for iframed page', function () { let ifr = null; let stringifyResponse = false; + let mockApi, replySent; beforeEach(function () { + mockApi = sinon.stub(); + replySent = defer(); sinon.stub(utils, 'logError'); sinon.stub(utils, 'logWarn'); ifr = createIFrameMarker(); @@ -322,17 +336,21 @@ describe('consentManagement', function () { function uspapiMessageHandler(event) { if (event && event.data) { - var data = event.data; + const data = event.data; if (data.__uspapiCall) { - var callId = data.__uspapiCall.callId; - var response = { - __uspapiReturn: { - callId, - returnValue: { uspString: '1YY' }, - success: true + const {command, version, callId} = data.__uspapiCall; + let response = mockApi(command, version, callId); + if (response) { + response = { + __uspapiReturn: { + callId, + returnValue: response, + success: true + } } - }; - event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + replySent.resolve(); + } } } } @@ -345,6 +363,11 @@ describe('consentManagement', function () { function testIFramedPage(testName, messageFormatString) { it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { stringifyResponse = messageFormatString; + mockApi.callsFake((cmd) => { + if (cmd === 'getUSPData') { + return { uspString: '1YY' } + } + }) setConsentConfig(goodConfig); requestBidsHook(() => { let consent = uspDataHandler.getConsentData(); @@ -355,6 +378,20 @@ describe('consentManagement', function () { }, {}); }); } + + it('fires deletion request on registerDeletion', (done) => { + mockApi.callsFake((cmd) => { + return cmd === 'registerDeletion' + }) + sinon.assert.notCalled(adapterManager.callDataDeletionRequest); + setConsentConfig(goodConfig); + replySent.promise.then(() => { + setTimeout(() => { // defer again to give time for the message to get through + sinon.assert.calledOnce(adapterManager.callDataDeletionRequest); + done() + }, 200) + }) + }); }); describe('test without iframe locater', function() { @@ -400,23 +437,19 @@ describe('consentManagement', function () { }); describe('USPAPI workflow for normal pages:', function () { - let uspapiStub = sinon.stub(); let ifr = null; beforeEach(function () { didHookReturn = false; ifr = createIFrameMarker(); - sinon.stub(utils, 'logError'); - sinon.stub(utils, 'logWarn'); + sandbox.stub(utils, 'logError'); + sandbox.stub(utils, 'logWarn'); window.__uspapi = function() {}; }); afterEach(function () { config.resetConfig(); $$PREBID_GLOBAL$$.requestBids.removeAll(); - uspapiStub.restore(); - utils.logError.restore(); - utils.logWarn.restore(); document.body.removeChild(ifr); delete window.__uspapi; resetConsentData(); @@ -427,7 +460,7 @@ describe('consentManagement', function () { uspString: '1NY' }; - uspapiStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + sandbox.stub(window, '__uspapi').callsFake((...args) => { args[2](testConsentData, true); }); @@ -448,7 +481,7 @@ describe('consentManagement', function () { uspString: '1NY' }; - uspapiStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + sandbox.stub(window, '__uspapi').callsFake((...args) => { args[2](testConsentData, true); }); @@ -463,6 +496,32 @@ describe('consentManagement', function () { expect(consentMeta.usp).to.equal(testConsentData.uspString); expect(consentMeta.generatedAt).to.be.above(1644367751709); }); + + it('registers deletion request event listener', () => { + let listener; + sandbox.stub(window, '__uspapi').callsFake((cmd, _, cb) => { + if (cmd === 'registerDeletion') { + listener = cb; + } + }); + setConsentConfig(goodConfig); + sinon.assert.notCalled(adapterManager.callDataDeletionRequest); + listener(); + sinon.assert.calledOnce(adapterManager.callDataDeletionRequest); + }); + + it('does not fail if CMP does not support registerDeletion', () => { + sandbox.stub(window, '__uspapi').callsFake((cmd, _, cb) => { + if (cmd === 'registerDeletion') { + throw new Error('CMP not compliant'); + } else if (cmd === 'getUSPData') { + // eslint-disable-next-line standard/no-callback-literal + cb({uspString: 'string'}, true); + } + }); + setConsentConfig(goodConfig); + expect(uspDataHandler.getConsentData()).to.eql('string'); + }); }); }); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 47a2e2ab3d9..506b88ad839 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,15 @@ -import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData, gdprScope } from 'modules/consentManagement.js'; +import { + setConsentConfig, + requestBidsHook, + resetConsentData, + userCMP, + consentTimeout, + actionTimeout, + staticConsentData, + gdprScope, + loadConsentData, + setActionTimeout +} from 'modules/consentManagement.js'; import { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -726,4 +737,41 @@ describe('consentManagement', function () { }); }); }); + + describe('actionTimeout', function () { + afterEach(function () { + config.resetConfig(); + resetConsentData(); + }); + + it('should set actionTimeout if present', () => { + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(5000); + expect(actionTimeout).to.be.equal(5500); + }); + + it('should utilize actionTimeout duration on initial user visit when user action is pending', () => { + const cb = () => {}; + const cmpCallMap = { + 'iab': () => {}, + 'static': () => {} + }; + const timeout = sinon.spy(); + + setConsentConfig({ + gdpr: { timeout: 5000, actionTimeout: 5500 } + }); + loadConsentData(cb, cmpCallMap, timeout); + + sinon.assert.calledWith(timeout, sinon.match.any, 5000); + + setActionTimeout(); + + timeout.lastCall.lastArg === 5500; + }); + }); }); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d1b310624a6..556dce447b9 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -625,6 +625,52 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); + it('should return a sync url if iframe syncs are enabled and GDPR applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and GDPR is undefined', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: undefined, + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=0&gdpr_consent=GDPR_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and USP applies', function () { + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?us_privacy=USP_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled, GDPR and USP applies', function () { + let gdprConsent = { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + } + let uspConsent = { + consentString: 'USP_CONSENT_STRING', + } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent, uspConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=1&gdpr_consent=GDPR_CONSENT_STRING&us_privacy=USP_CONSENT_STRING'); + }) + it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { let syncOptions = {'pixelEnabled': true}; let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE]); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 7af5f5d77a2..da1a0ca7f32 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1156,6 +1156,18 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request with first party data', function () { const siteData = { keywords: ['power tools'], + content: { + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }] + }, ext: { data: { pageType: 'article' @@ -1164,6 +1176,16 @@ describe('The Criteo bidding adapter', function () { }; const userData = { gender: 'M', + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }], ext: { data: { registered: true @@ -1203,7 +1225,8 @@ describe('The Criteo bidding adapter', function () { const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); expect(request.data.publisher.ext).to.deep.equal({ data: { pageType: 'article' } }); - expect(request.data.user.ext).to.deep.equal({ data: { registered: true } }); + expect(request.data.user).to.deep.equal(userData); + expect(request.data.site).to.deep.equal(siteData); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, data: { @@ -1279,6 +1302,34 @@ describe('The Criteo bidding adapter', function () { }); }); + it('should properly build a request with static floors', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + bidFloor: 1, + bidFloorCur: 'EUR' + }, + }, + ]; + const bidderRequest = {}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].ext.floors).to.deep.equal({ + 'banner': { + '300x250': { 'currency': 'EUR', 'floor': 1 }, + '728x90': { 'currency': 'EUR', 'floor': 1 } + } + }); + }); + it('should properly build a video request with several player sizes with floors', function () { const bidRequests = [ { diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index 6eb4f929a42..b674cb5976d 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -351,6 +351,11 @@ describe('currency', function () { }); describe('currency.addBidResponseDecorator', function () { + let reject; + beforeEach(() => { + reject = sinon.stub().returns({status: 'rejected'}); + }); + it('should leave bid at 1 when currency support is not enabled and fromCurrency is USD', function () { setConfig({}); var bid = makeBid({ 'cpm': 1, 'currency': 'USD' }); @@ -368,8 +373,9 @@ describe('currency', function () { var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; - }, 'elementId', bid); - expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); + }, 'elementId', bid, reject); + expect(innerBid.status).to.equal('rejected'); + expect(reject.calledOnce).to.be.true; }); it('should not buffer bid when currency is already in desired currency', function () { @@ -395,8 +401,9 @@ describe('currency', function () { var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; - }, 'elementId', bid); - expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); + }, 'elementId', bid, reject); + expect(innerBid.status).to.equal('rejected'); + expect(reject.calledOnce).to.be.true; }); it('should result in NO_BID when adServerCurrency is not supported in file', function () { @@ -407,8 +414,9 @@ describe('currency', function () { var innerBid; addBidResponseHook(function(adCodeId, bid) { innerBid = bid; - }, 'elementId', bid); - expect(innerBid.statusMessage).to.equal('Bid returned empty or error response'); + }, 'elementId', bid, reject); + expect(innerBid.status).to.equal('rejected'); + expect(reject.calledOnce).to.be.true; }); it('should return 1 when currency support is enabled and same currency code is requested as is set to adServerCurrency', function () { diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index f116b184b8c..88c54212aff 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -1,382 +1,296 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { config } from '../../../src/config.js'; -import { - spec, - CW_PAGE_VIEW_ID, - ENDPOINT_URL, - RENDERER_URL, -} from '../../../modules/cwireBidAdapter.js'; -import * as prebidGlobal from 'src/prebidGlobal.js'; - -// ------------------------------------ -// Bid Request Builder -// ------------------------------------ - -const BID_DEFAULTS = { - request: { - bidder: 'cwire', - auctionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - transactionId: 'txaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', - bidId: 'bid123445', - bidderRequestId: 'brid12345', - code: 'original-div', - }, - params: { - placementId: 123456, - pageId: 777, - }, - sizes: [[300, 250], [1, 1]], -}; - -const BidderRequestBuilder = function BidderRequestBuilder(options) { - const defaults = { - bidderCode: 'cwire', - auctionId: BID_DEFAULTS.request.auctionId, - bidderRequestId: BID_DEFAULTS.request.bidderRequestId, - transactionId: BID_DEFAULTS.request.transactionId, - timeout: 3000, - }; - - const request = { - ...defaults, - ...options - }; - - this.build = () => request; -}; - -const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { - const defaults = JSON.parse(JSON.stringify(BID_DEFAULTS)); - - const request = { - ...defaults.request, - ...options - }; - - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request[k]; - }) - } - - this.withParams = (options, deleteKeys) => { - request.params = { - ...defaults.params, - ...options - }; - if (request && utils.isArray(deleteKeys)) { - deleteKeys.forEach((k) => { - delete request.params[k]; - }) - } - return this; - }; - - this.build = () => request; -}; +import {expect} from 'chai'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import {BID_ENDPOINT, spec, storage} from '../../../modules/cwireBidAdapter'; +import {deepClone, logInfo} from '../../../src/utils'; +import * as utils from 'src/utils.js'; +import {sandbox, stub} from 'sinon'; +import {config} from '../../../src/config'; describe('C-WIRE bid adapter', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); + config.setConfig({debug: true}); + const adapter = newBidder(spec); + let bidRequests = [ + { + 'bidder': 'cwire', + 'params': { + 'pageId': '4057', + 'placementId': 'ad-slot-bla' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + const response = { + body: { + 'cwid': '2ef90743-7936-4a82-8acf-e73382a64e94', + 'hash': '17112D98BBF55D3A', + 'bids': [{ + 'html': '

Hello world

', + 'cpm': 100, + 'currency': 'CHF', + 'dimensions': [1, 1], + 'netRevenue': true, + 'creativeId': '3454', + 'requestId': '2c634d4ca5ccfb', + 'placementId': 177, + 'transactionId': 'b4b32618-1350-4828-b6f0-fbb5c329e9a4', + 'ttl': 360 + }] + } + } + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + expect(spec.isBidRequestValid).to.exist.and.to.be.a('function'); + expect(spec.buildRequests).to.exist.and.to.be.a('function'); + expect(spec.interpretResponse).to.exist.and.to.be.a('function'); + }); }); - - afterEach(() => { - sandbox.restore(); - config.resetConfig(); + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(BID_ENDPOINT); + expect(request.method).to.equal('POST'); + }); }); + describe('buildRequests with given creative', function () { + let utilsStub; - // START TESTING - describe('C-WIRE - isBidRequestValid', function () { - it('should return true when required params found', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - expect(spec.isBidRequestValid(bid01)).to.equal(true); + before(function () { + utilsStub = stub(utils, 'getParameterByName').callsFake(function () { + return 'str-str' + }); }); - it('should fail if there is no placementId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + after(function () { + utilsStub.restore(); }); - it('should fail if invalid placementId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.placementId; - bid01.placementId = '322'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + it('should add creativeId if url parameter given', function () { + // set from bid.params + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + expect(payload.cwcreative).to.exist; + expect(payload.cwcreative).to.deep.equal('str-str'); + }); + }) + + describe('buildRequests reads adUnit offsetWidth and offsetHeight', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + offsetWidth: 200, + offsetHeight: 250 + }); }); + it('width and height should be set', function () { + let bidRequest = deepClone(bidRequests[0]); - it('should fail if there is no pageId', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId - expect(spec.isBidRequestValid(bid01)).to.equal(false); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) + + expect(el).to.exist; + expect(payload.slots[0].cwExt.dimensions.width).to.equal(200); + expect(payload.slots[0].cwExt.dimensions.height).to.equal(250); + expect(payload.slots[0].cwExt.style.maxHeight).to.not.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.not.exist; + }); + after(function () { + sandbox.restore() + }); + }); + describe('buildRequests reads style attributes', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + style: { + maxWidth: '400px', + maxHeight: '350px', + } + }); }); + it('css maxWidth should be set', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + const el = document.getElementById(`${bidRequest.adUnitCode}`) + + logInfo(JSON.stringify(payload)) - it('should fail if invalid pageId type', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.pageId; - bid01.params.pageId = '3320'; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + expect(el).to.exist; + expect(payload.slots[0].cwExt.style.maxWidth).to.eq('400px'); + !expect(payload.slots[0].cwExt.style.maxHeight).to.eq('350px'); }); + after(function () { + sandbox.restore() + }); + }); - it('should fail if cwcreative of type number', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - delete bid01.params.cwcreative; - bid01.params.cwcreative = 3320; - expect(spec.isBidRequestValid(bid01)).to.equal(false); + describe('buildRequests reads feature flags', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'feature1,feature2' + }); }); - it('should pass with valid cwcreative of type string', function () { - const bid01 = new BidRequestBuilder().withParams().build(); - bid01.params.cwcreative = 'i-am-a-string'; - expect(spec.isBidRequestValid(bid01)).to.equal(true); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.featureFlags).to.exist; + expect(payload.featureFlags).to.include.members(['feature1', 'feature2']); + }); + after(function () { + sandbox.restore() }); }); - describe('C-WIRE - buildRequests()', function () { - it('creates a valid request', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz-uuid', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('54321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz-uuid'); - expect(requests.data.refgroups[0]).to.equal('group_1'); + describe('buildRequests reads cwgroups flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'group1,group2' + }); }); - it('creates a valid request - read debug params from second bid', function () { - const bid01 = new BidRequestBuilder().withParams().build(); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); - const bid02 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('1234'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.refgroups).to.exist; + expect(payload.refgroups).to.include.members(['group1', 'group2']); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - read debug params from first bid, ignore second', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - cwapikey: 'api_key_33', - refgroups: 'group_33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwcreative: '1234', - cwapikey: 'api_key_5', - refgroups: 'group_5', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02], bidderRequest01); - - expect(requests.data.slots.length).to.equal(2); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_33'); - expect(requests.data.refgroups[0]).to.equal('group_33'); + describe('buildRequests reads debug flag', function () { + before(function () { + sandbox.stub(utils, 'getParameterByName').callsFake(function () { + return 'true' + }); }); - it('creates a valid request - read debug params from 3 different slots', function () { - const bid01 = new BidRequestBuilder() - .withParams({ - cwcreative: '33', - }).build(); - - const bid02 = new BidRequestBuilder() - .withParams({ - cwapikey: 'api_key_5', - }).build(); - - const bid03 = new BidRequestBuilder() - .withParams({ - refgroups: 'group_5', - }).build(); - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01, bid02, bid03], bidderRequest01); - - expect(requests.data.slots.length).to.equal(3); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal('33'); - expect(requests.data.cwapikey).to.equal('api_key_5'); - expect(requests.data.refgroups[0]).to.equal('group_5'); + it('read from url parameter', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + logInfo(JSON.stringify(payload)) + + expect(payload.debug).to.exist; + expect(payload.debug).to.equal(true); + }); + after(function () { + sandbox.restore() }); + }) - it('creates a valid request - config is overriden by URL params', function () { - // for whatever reason stub for getWindowLocation does not work - // so this was the closest way to test for get params - const params = sandbox.stub(utils, 'getParameterByName'); - params.withArgs('cwgroups').returns('group_2'); - params.withArgs('cwcreative').returns('654321'); - - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams({ - cwcreative: '54321', - cwapikey: 'xxx-xxx-yyy-zzz', - refgroups: 'group_1', - }).build(); - - const bidderRequest01 = new BidderRequestBuilder().build(); - - const requests = spec.buildRequests([bid01], bidderRequest01); - - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal('654321'); - expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz'); - expect(requests.data.refgroups[0]).to.equal('group_2'); + describe('buildRequests reads cw_id from Localstorage', function () { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => 'taerfagerg'); }); - it('creates a valid request - if params are not set, null or empty array are sent to the API', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + it('cw_id is set', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const requests = spec.buildRequests([bid01], bidderRequest01); + logInfo(JSON.stringify(payload)) - expect(requests.data.slots.length).to.equal(1); - expect(requests.data.cwid).to.be.null; - expect(requests.data.cwid).to.be.null; - expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal(null); - expect(requests.data.cwapikey).to.equal(null); - expect(requests.data.refgroups.length).to.equal(0); + expect(payload.cwid).to.exist; + expect(payload.cwid).to.equal('taerfagerg'); }); - }); + after(function () { + sandbox.restore() + }); + }) - describe('C-WIRE - interpretResponse()', function () { - const serverResponse = { - body: { - bids: [{ - html: '

AD CONTENT

', - currency: 'CHF', - cpm: 43.37, - dimensions: [1, 1], - netRevenue: true, - creativeId: '1337', - requestId: BID_DEFAULTS.request.bidId, - ttl: 3500, - }], - } - }; - - const expectedResponse = [{ - ad: JSON.parse(JSON.stringify(serverResponse.body.bids[0].html)), - bidderCode: BID_DEFAULTS.request.bidder, - cpm: JSON.parse(JSON.stringify(serverResponse.body.bids[0].cpm)), - creativeId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].creativeId)), - currency: JSON.parse(JSON.stringify(serverResponse.body.bids[0].currency)), - height: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[0])), - width: JSON.parse(JSON.stringify(serverResponse.body.bids[0].dimensions[1])), - netRevenue: JSON.parse(JSON.stringify(serverResponse.body.bids[0].netRevenue)), - requestId: JSON.parse(JSON.stringify(serverResponse.body.bids[0].requestId)), - ttl: JSON.parse(JSON.stringify(serverResponse.body.bids[0].ttl)), - meta: { - advertiserDomains: [], - }, - mediaType: 'banner', - }] - - it('correctly parses response', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - banner: { - sizes: [[1, 1]], - } - } - }).withParams().build(); + describe('buildRequests maps flattens params for legacy compat', function () { + before(function () { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({}); + }); + it('pageId flattened', function () { + let bidRequest = deepClone(bidRequests[0]); - const bidderRequest01 = new BidderRequestBuilder().build(); - const requests = spec.buildRequests([bid01], bidderRequest01); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - const response = spec.interpretResponse(serverResponse, requests); - expect(response).to.deep.equal(expectedResponse); + logInfo(JSON.stringify(payload)) + + expect(payload.slots[0].pageId).to.exist; + }); + after(function () { + sandbox.restore() }); + }) - it('attaches renderer', function () { - const bid01 = new BidRequestBuilder({ - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'outstream', - } - } - }).withParams().build(); - const bidderRequest01 = new BidderRequestBuilder().build(); + describe('pageId and placementId are required params', function () { + it('invalid request', function () { + let bidRequest = deepClone(bidRequests[0]); + delete bidRequest.params + + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.false; + }) - const _serverResponse = utils.deepClone(serverResponse); - _serverResponse.body.bids[0].vastXml = ''; + it('valid request', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const _expectedResponse = utils.deepClone(expectedResponse); - _expectedResponse[0].mediaType = 'video'; - _expectedResponse[0].videoScript = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].html)); - _expectedResponse[0].vastXml = JSON.parse(JSON.stringify(_serverResponse.body.bids[0].vastXml)); - delete _expectedResponse[0].ad; + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - const requests = spec.buildRequests([bid01], bidderRequest01); - expect(requests.data.slots[0].sizes).to.deep.equal(['640x480']); + it('cwcreative must be of type string', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params.pageId = 42 + bidRequest.params.placementId = 42 - const response = spec.interpretResponse(_serverResponse, requests); - expect(response[0].renderer).to.exist; - expect(response[0].renderer.url).to.equals(RENDERER_URL); - expect(response[0].renderer.loaded).to.equals(false); + const valid = spec.isBidRequestValid(bidRequest); + expect(valid).to.be.true; + }) - delete response[0].renderer; - expect(response).to.deep.equal(_expectedResponse); - }); + it('build request adds pageId', function () { + let bidRequest = deepClone(bidRequests[0]); + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.slots[0].pageId).to.exist; + }) + }); + + describe('process serverResponse', function () { + it('html to ad mapping', function () { + let bidResponse = deepClone(response); + const bids = spec.interpretResponse(bidResponse, {}); + + expect(bids[0].ad).to.exist; + }) }); }); diff --git a/test/spec/modules/dacIdSystem_spec.js b/test/spec/modules/dacIdSystem_spec.js index d78b4a69000..0246e65a310 100644 --- a/test/spec/modules/dacIdSystem_spec.js +++ b/test/spec/modules/dacIdSystem_spec.js @@ -1,8 +1,16 @@ -import { dacIdSystemSubmodule, storage, cookieKey } from 'modules/dacIdSystem.js'; +import { + dacIdSystemSubmodule, + storage, + FUUID_COOKIE_NAME, + AONEID_COOKIE_NAME +} from 'modules/dacIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; -const DACID_DUMMY_VALUE = 'dacIdTest'; +const FUUID_DUMMY_VALUE = 'dacIdTest'; +const AONEID_DUMMY_VALUE = '12345' const DACID_DUMMY_OBJ = { - dacId: DACID_DUMMY_VALUE + fuuid: FUUID_DUMMY_VALUE, + uid: AONEID_DUMMY_VALUE }; describe('dacId module', function () { @@ -23,24 +31,98 @@ describe('dacId module', function () { '' ] + const configParamTestCase = { + params: { + oid: [ + '637c1b6fc26bfad0', // valid + 'e8316b39c08029e1' // invalid + ] + } + } + describe('getId()', function () { - it('should return the uid when it exists in cookie', function () { - getCookieStub.withArgs(cookieKey).returns(DACID_DUMMY_VALUE); + it('should return undefined when oid & fuuid not exist', function () { + // no oid, no fuuid const id = dacIdSystemSubmodule.getId(); - expect(id).to.be.deep.equal({id: {dacId: DACID_DUMMY_VALUE}}); + expect(id).to.equal(undefined); }); - cookieTestCasesForEmpty.forEach(testCase => it('should return the uid when it not exists in cookie', function () { - getCookieStub.withArgs(cookieKey).returns(testCase); + it('should return fuuid when oid not exists but fuuid exists', function () { + // no oid, fuuid + getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE); const id = dacIdSystemSubmodule.getId(); - expect(id).to.be.deep.equal(undefined); + expect(id).to.be.deep.equal({ + id: { + fuuid: FUUID_DUMMY_VALUE, + uid: undefined + } + }); + }); + + it('should return fuuid when oid is invalid but fuuid exists', function () { + // invalid oid, fuuid, no AoneId + getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE); + const id = dacIdSystemSubmodule.getId(configParamTestCase.params.oid[1]); + expect(id).to.be.deep.equal({ + id: { + fuuid: FUUID_DUMMY_VALUE, + uid: undefined + } + }); + }); + + cookieTestCasesForEmpty.forEach(testCase => it('should return undefined when fuuid not exists', function () { + // valid oid, no fuuid, no AoneId + getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(testCase); + const id = dacIdSystemSubmodule.getId(configParamTestCase.params.oid[0]); + expect(id).to.equal(undefined); })); + + it('should return AoneId when AoneId not exists', function () { + // valid oid, fuuid, no AoneId + getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE); + const callbackSpy = sinon.spy(); + const callback = dacIdSystemSubmodule.getId({params: {oid: configParamTestCase.params.oid[0]}}).callback; + callback(callbackSpy); + const request = server.requests[0]; + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({'uid': AONEID_DUMMY_VALUE})); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({fuuid: 'dacIdTest', uid: AONEID_DUMMY_VALUE}); + }); + + cookieTestCasesForEmpty.forEach(testCase => it('should return undefined when AoneId not exists & API result is empty', function () { + // valid oid, fuuid, no AoneId, API result empty + getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE); + const callbackSpy = sinon.spy(); + const callback = dacIdSystemSubmodule.getId({params: {oid: configParamTestCase.params.oid[0]}}).callback; + callback(callbackSpy); + const request = server.requests[0]; + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({'uid': testCase})); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({fuuid: 'dacIdTest', uid: undefined}); + })); + + it('should return the fuuid & AoneId when they exist', function () { + // valid oid, fuuid, AoneId + getCookieStub.withArgs(FUUID_COOKIE_NAME).returns(FUUID_DUMMY_VALUE); + getCookieStub.withArgs(AONEID_COOKIE_NAME).returns(AONEID_DUMMY_VALUE); + const id = dacIdSystemSubmodule.getId(configParamTestCase.params.oid[0]); + expect(id).to.be.deep.equal({ + id: { + fuuid: FUUID_DUMMY_VALUE, + uid: AONEID_DUMMY_VALUE + } + }); + }); }); describe('decode()', function () { - it('should return the uid when it exists in cookie', function () { + it('should return fuuid & AoneId when they exist', function () { const decoded = dacIdSystemSubmodule.decode(DACID_DUMMY_OBJ); - expect(decoded).to.be.deep.equal({dacId: {id: DACID_DUMMY_VALUE}}); + expect(decoded).to.be.deep.equal({ + dacId: { + fuuid: FUUID_DUMMY_VALUE, + id: AONEID_DUMMY_VALUE + } + }); }); it('should return the undefined when decode id is not "string"', function () { diff --git a/test/spec/modules/dgkeywordRtdProvider_spec.js b/test/spec/modules/dgkeywordRtdProvider_spec.js index 16f2906b681..754740b7a64 100644 --- a/test/spec/modules/dgkeywordRtdProvider_spec.js +++ b/test/spec/modules/dgkeywordRtdProvider_spec.js @@ -383,10 +383,11 @@ describe('Digital Garage Keyword Module', function () { } let moduleConfig = cloneDeep(DEF_CONFIG); window.localStorage.setItem('ope_fpid', uuid); + moduleConfig.params.enableReadFpid = true; dgRtd.getDgKeywordsAndSet( pbjs, () => { - const url = dgRtd.getProfileApiUrl(null); + const url = dgRtd.getProfileApiUrl(null, moduleConfig.params.enableReadFpid); expect(url.indexOf(uuid) > 0).to.equal(true); expect(url).to.equal(server.requests[0].url); done(); @@ -410,11 +411,10 @@ describe('Digital Garage Keyword Module', function () { } let moduleConfig = cloneDeep(DEF_CONFIG); window.localStorage.setItem('ope_fpid', uuid); - moduleConfig.params.disableReadFpid = true; dgRtd.getDgKeywordsAndSet( pbjs, () => { - const url = dgRtd.getProfileApiUrl(null, moduleConfig.params.disableReadFpid); + const url = dgRtd.getProfileApiUrl(null); expect(url.indexOf(uuid) > 0).to.equal(false); expect(url).to.equal(server.requests[0].url); done(); diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index a30afd96ad1..078add73046 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -1,14 +1,14 @@ import { expect } from 'chai'; import { spec } from 'modules/discoveryBidAdapter.js'; -describe('DiscoveryDSP:BidAdapterTests', function () { +describe('discovery:BidAdapterTests', function () { let bidRequestData = { - bidderCode: 'DiscoveryDSP', + bidderCode: 'discovery', auctionId: 'ff66e39e-4075-4d18-9854-56fde9b879ac', bidderRequestId: '4fec04e87ad785', bids: [ { - bidder: 'DiscoveryDSP', + bidder: 'discovery', params: { token: 'd0f4902b616cc5c38cbe0a08676d0ed9', }, @@ -32,25 +32,26 @@ describe('DiscoveryDSP:BidAdapterTests', function () { }; let request = []; - it('DiscoveryDSP:validate_pub_params', function () { + it('discovery:validate_pub_params', function () { expect( spec.isBidRequestValid({ - bidder: 'DiscoveryDSP', + bidder: 'discovery', params: { token: ['d0f4902b616cc5c38cbe0a08676d0ed9'], - media: ['test_media'] + tagid: ['test_tagid'], + publisher: ['test_publisher'] }, }) ).to.equal(true); }); - it('DiscoveryDSP:validate_generated_params', function () { + it('discovery:validate_generated_params', function () { request = spec.buildRequests(bidRequestData.bids, bidRequestData); let req_data = JSON.parse(request.data); expect(req_data.imp).to.have.lengthOf(1); }); - it('DiscoveryDSP:validate_response_params', function () { + it('discovery:validate_response_params', function () { let tempAdm = '' tempAdm += '%3Cscr'; tempAdm += 'ipt%3E'; diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 6872145710c..13f89b054bd 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -392,7 +392,7 @@ describe('eids array generation for known sub-modules', function() { const [eid] = createEidsArray(userId); expect(eid).to.deep.equal({ - source: 'amxrtb.com', + source: 'amxdt.net', uids: [{ atype: 1, id, @@ -444,11 +444,15 @@ describe('eids array generation for known sub-modules', function() { describe('ftrackId', () => { it('should return the correct EID schema', () => { + // This is the schema returned from the ftrack decode() method expect(createEidsArray({ ftrackId: { - DeviceID: ['aaa', 'bbb'], - SingleDeviceID: ['ccc', 'ddd'], - HHID: ['eee', 'fff'] + uid: 'test-device-id', + ext: { + DeviceID: 'test-device-id', + SingleDeviceID: 'test-single-device-id', + HHID: 'test-household-id' + } }, foo: { bar: 'baz' @@ -457,13 +461,16 @@ describe('eids array generation for known sub-modules', function() { ipsum: '' } })).to.deep.equal([{ - atype: 1, - id: 'aaa|bbb', - ext: { - DeviceID: 'aaa|bbb', - SingleDeviceID: 'ccc|ddd', - HHID: 'eee|fff' - } + source: 'flashtalking.com', + uids: [{ + atype: 1, + id: 'test-device-id', + ext: { + DeviceID: 'test-device-id', + SingleDeviceID: 'test-single-device-id', + HHID: 'test-household-id' + } + }] }]); }); }); diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index d99318b5ddc..d80d0f3e875 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -707,7 +707,7 @@ describe('emx_digital Adapter', function () { })); }); - it('returns valid advertiser domain', function () { + it('returns valid advertiser domains', function () { const bidResponse = utils.deepClone(serverResponse); let result = spec.interpretResponse({body: bidResponse}); expect(result[0].meta.advertiserDomains).to.deep.equal(expectedResponse[0].meta.advertiserDomains); @@ -724,6 +724,7 @@ describe('emx_digital Adapter', function () { expect(syncs).to.not.be.an('undefined'); expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html') }); it('should pass gdpr params', function () { @@ -734,6 +735,34 @@ describe('emx_digital Adapter', function () { expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); expect(syncs[0].url).to.contains('gdpr=0'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=0&gdpr_consent=test') + }); + + it('should pass us_privacy string', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, { + consentString: 'test', + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('usp=test'); + }); + + it('should pass us_privacy and gdpr strings', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, + { + gdprApplies: true, + consentString: 'test' + }, + { + consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=1'); + expect(syncs[0].url).to.contains('usp=test'); + expect(syncs[0].url).to.equal('https://biddr.brealtime.com/check.html?gdpr=1&gdpr_consent=test&usp=test') }); }); }); diff --git a/test/spec/modules/enrichmentFpdModule_spec.js b/test/spec/modules/enrichmentFpdModule_spec.js index 1c6cab5afec..e69de29bb2d 100644 --- a/test/spec/modules/enrichmentFpdModule_spec.js +++ b/test/spec/modules/enrichmentFpdModule_spec.js @@ -1,158 +0,0 @@ -import { expect } from 'chai'; -import { getRefererInfo } from 'src/refererDetection.js'; -import {processFpd, coreStorage, resetEnrichments} from 'modules/enrichmentFpdModule.js'; -import * as enrichmentModule from 'modules/enrichmentFpdModule.js'; -import {GreedyPromise} from '../../../src/utils/promise.js'; - -describe('the first party data enrichment module', function() { - let width; - let widthStub; - let height; - let heightStub; - let querySelectorStub; - let coreStorageStub; - let canonical; - let keywords; - let lowEntropySuaStub; - let highEntropySuaStub; - - before(function() { - canonical = document.createElement('link'); - canonical.rel = 'canonical'; - keywords = document.createElement('meta'); - keywords.name = 'keywords'; - }); - - beforeEach(function() { - resetEnrichments(); - querySelectorStub = sinon.stub(window.top.document, 'querySelector'); - querySelectorStub.withArgs("link[rel='canonical']").returns(canonical); - querySelectorStub.withArgs("meta[name='keywords']").returns(keywords); - widthStub = sinon.stub(window.top, 'innerWidth').get(function() { - return width; - }); - heightStub = sinon.stub(window.top, 'innerHeight').get(function() { - return height; - }); - coreStorageStub = sinon.stub(coreStorage, 'getCookie'); - coreStorageStub - .onFirstCall() - .returns(null) // co.uk - .onSecondCall() - .returns('writeable'); // domain.co.uk - lowEntropySuaStub = sinon.stub(enrichmentModule.sua, 'le').callsFake(() => null); - highEntropySuaStub = sinon.stub(enrichmentModule.sua, 'he').callsFake(() => GreedyPromise.resolve()); - }); - - afterEach(function() { - widthStub.restore(); - heightStub.restore(); - querySelectorStub.restore(); - coreStorageStub.restore(); - canonical = document.createElement('link'); - canonical.rel = 'canonical'; - keywords = document.createElement('meta'); - keywords.name = 'keywords'; - lowEntropySuaStub.restore(); - highEntropySuaStub.restore(); - }); - - function syncProcessFpd(fpdConf, ortb2Fragments) { - let result; - processFpd(fpdConf, ortb2Fragments).then((res) => { result = res; }); - return result; - }; - - it('adds ref and device values', function() { - width = 800; - height = 500; - - let validated = syncProcessFpd({}, {}).global; - - const {ref, page, domain} = getRefererInfo(); - expect(validated.site.ref).to.equal(ref || undefined); - expect(validated.site.page).to.equal(page || undefined) - expect(validated.site.domain).to.equal(domain || undefined) - expect(validated.device).to.deep.equal({ w: 800, h: 500 }); - expect(validated.site.keywords).to.be.undefined; - }); - - it('adds page domain values if canonical url exists', function() { - width = 800; - height = 500; - canonical.href = 'https://www.subdomain.domain.co.uk/path?query=12345'; - - let validated = syncProcessFpd({}, {}).global; - - expect(validated.site.ref).to.equal(getRefererInfo().ref || undefined); - expect(validated.site.page).to.equal('https://www.subdomain.domain.co.uk/path?query=12345'); - expect(validated.site.domain).to.equal('subdomain.domain.co.uk'); - expect(validated.site.publisher.domain).to.equal('domain.co.uk'); - expect(validated.device).to.deep.equal({ w: 800, h: 500 }); - expect(validated.site.keywords).to.be.undefined; - }); - - it('adds keyword value if keyword meta content exists', function() { - width = 800; - height = 500; - keywords.content = 'value1,value2,value3'; - - let validated = syncProcessFpd({}, {}).global; - - expect(validated.site.keywords).to.equal('value1,value2,value3'); - }); - - it('does not overwrite existing data from getConfig ortb2', function() { - width = 800; - height = 500; - - let validated = syncProcessFpd({}, {global: {device: {w: 1200, h: 700}, site: {ref: 'https://someUrl.com', page: 'test.com'}}}).global; - - expect(validated.site.ref).to.equal('https://someUrl.com'); - expect(validated.site.page).to.equal('test.com'); - expect(validated.device).to.deep.equal({ w: 1200, h: 700 }); - expect(validated.site.keywords).to.be.undefined; - }); - - it('does not run enrichments again on the second call', () => { - width = 1; - height = 2; - const first = syncProcessFpd({}, {}).global; - width = 3; - const second = syncProcessFpd({}, {}).global; - expect(first).to.eql(second); - }); - - describe('device.sua', () => { - it('does not set device.sua if resolved sua is null', () => { - const sua = syncProcessFpd({}, {}).global.device?.sua; - expect(sua).to.not.exist; - }); - it('uses low entropy values if uaHints is []', () => { - lowEntropySuaStub.callsFake(() => ({mock: 'sua'})); - const sua = syncProcessFpd({uaHints: []}, {}).global.device.sua; - expect(sua).to.eql({mock: 'sua'}); - }); - it('uses high entropy values otherwise', () => { - highEntropySuaStub.callsFake((hints) => GreedyPromise.resolve({hints})); - const sua = syncProcessFpd({uaHints: ['h1', 'h2']}, {}).global.device.sua; - expect(sua).to.eql({hints: ['h1', 'h2']}); - }) - }) - - it('should store a reference to gpc witin ortb2.regs.ext if it has been enabled', function() { - let validated; - width = 800; - height = 500; - - validated = syncProcessFpd({}, {}).global; - expect(validated.regs).to.equal(undefined); - - resetEnrichments(); - - const globalPrivacyControlStub = sinon.stub(window, 'navigator').value({globalPrivacyControl: true}); - validated = syncProcessFpd({}, {}).global; - expect(validated.regs.ext.gpc).to.equal(1); - globalPrivacyControlStub.restore(); - }); -}); diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index bb9e6c4fb86..419181de983 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -152,9 +152,6 @@ describe('eplanning analytics adapter', function () { // Step 10 check that the host to send the ajax request is configurable via options expect(eplAnalyticsAdapter.context.host).to.equal(initOptions.host); - - // Step 11 verify that we received 7 events (6 E-Planning events + 1 Clean.io event) - sinon.assert.callCount(eplAnalyticsAdapter.track, 7); }); }); }); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index db70f7956d7..cb8393a29b8 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -7,6 +7,7 @@ import * as utils from 'src/utils.js'; import {hook} from '../../../src/hook.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import { makeSlot } from '../integration/faker/googletag.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; describe('E-Planning Adapter', function () { const adapter = newBidder('spec'); @@ -19,6 +20,7 @@ describe('E-Planning Adapter', function () { const CLEAN_ADUNIT_CODE2 = '300x250_1'; const CLEAN_ADUNIT_CODE = '300x250_0'; const CLEAN_ADUNIT_CODE_ML = 'adunitco_de'; + const CLEAN_ADUNIT_CODE_VAST = 'VIDEO_300x250_0'; const BID_ID = '123456789'; const BID_ID2 = '987654321'; const BID_ID3 = '998877665'; @@ -30,6 +32,9 @@ describe('E-Planning Adapter', function () { const CRID = '1234567890'; const TEST_ISV = 'leles.e-planning.net'; const ADOMAIN = 'adomain.com'; + const ADM_VAST = '\n\n\nAcudeo Compatible\n\nhttp://demo.tremormedia.com/proddev/vast/vast_inline_linear.xml\n\n&nfc=1&S=<% SERVER_ID %>&rnd=733663791&bk=0123456789abcdef]]>http://myTrackingURL/wrapper/impression\n\n\n\n\n\n\n\n\n\n\nhttp://demo.tremormedia.com/proddev/vast/300x250_banner1.jpg\n\n\n\nhttp://myTrackingURL/wrapper/firstCompanionCreativeView\n\n\nhttp://www.tremormedia.com\n\n\n\nhttp://demo.tremormedia.com/proddev/vast/728x90_banner1.jpg\n\nhttp://www.tremormedia.com\n\n\n\n\n\n\n\n'; + const ADM_VAST_VV_1 = 'test'; + const DEFAULT_SIZE_VAST = '640x480'; const validBid = { 'bidder': 'eplanning', 'bidId': BID_ID, @@ -69,6 +74,110 @@ describe('E-Planning Adapter', function () { }, 'sizes': [[300, 250], [300, 600]], }; + const validBidSpaceOutstream = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'sn': SN, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [300, 600], + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6], + 'playbackmethod': [2], + 'skip': 1 + } + }, + }; + const validBidOutstreamNoSize = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'sn': SN, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6], + 'playbackmethod': [2], + 'skip': 1 + } + }, + }; + const validBidOutstreamNSizes = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'sn': SN, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': [[300, 600], [400, 500], [500, 500]], + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6], + 'playbackmethod': [2], + 'skip': 1 + } + }, + }; + const bidOutstreamInvalidSizes = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'sn': SN, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'playerSize': 'invalidSize', + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6], + 'playbackmethod': [2], + 'skip': 1 + } + }, + }; + const validBidSpaceInstream = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'sn': SN, + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6], + 'playbackmethod': [2], + 'skip': 1 + } + }, + }; + const validBidSpaceVastNoContext = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'sn': SN, + }, + 'mediaTypes': { + 'video': { + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6], + 'playbackmethod': [2], + 'skip': 1 + } + }, + }; const validBidView = { 'bidder': 'eplanning', 'bidId': BID_ID, @@ -145,6 +254,14 @@ describe('E-Planning Adapter', function () { } } }; + const validBidNoSize = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + 'sn': SN, + } + }; const response = { body: { 'sI': { @@ -176,6 +293,68 @@ describe('E-Planning Adapter', function () { ] } }; + const responseVast = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE_VAST, + 'a': [{ + 'adm': ADM_VAST, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; + const responseVastVV1 = { + body: { + 'sI': { + 'k': '12345' + }, + 'sec': { + 'k': 'ROS' + }, + 'sp': [{ + 'k': CLEAN_ADUNIT_CODE_VAST, + 'a': [{ + 'adm': ADM_VAST_VV_1, + 'id': '7854abc56248f874', + 'i': I_ID, + 'fi': '7854abc56248f872', + 'ip': '45621afd87462104', + 'w': W, + 'h': H, + 'crid': CRID, + 'pr': CPM + }], + }], + 'cs': [ + 'http://a-sync-url.com/', + { + 'u': 'http://another-sync-url.com/test.php?&partner=123456&endpoint=us-east', + 'ifr': true + } + ] + } + }; const responseWithTwoAdunits = { body: { 'sI': { @@ -411,6 +590,77 @@ describe('E-Planning Adapter', function () { expect(e).to.equal('300x250_0:300x250,300x600+100x100_0:100x100'); }); + it('should return correct e parameter with support vast with one space with size outstream', function () { + let bidRequests = [validBidSpaceOutstream]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_300x600_0:300x600;1'); + expect(data.vctx).to.equal(2); + expect(data.vv).to.equal(3); + }); + + it('should correctly return the e parameter with n sizes in playerSize', function () { + let bidRequests = [validBidOutstreamNSizes]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_300x600_0:300x600;1'); + expect(data.vctx).to.equal(2); + expect(data.vv).to.equal(3); + }); + + it('should correctly return the e parameter with invalid sizes in playerSize', function () { + let bidRequests = [bidOutstreamInvalidSizes]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_' + DEFAULT_SIZE_VAST + '_0:' + DEFAULT_SIZE_VAST + ';1'); + expect(data.vctx).to.equal(2); + expect(data.vv).to.equal(3); + }); + + it('should return correct e parameter with support vast with one space with size default outstream', function () { + let bidRequests = [validBidOutstreamNoSize]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_640x480_0:640x480;1'); + expect(data.vctx).to.equal(2); + expect(data.vv).to.equal(3); + }); + + it('should return correct e parameter with support vast with one space with size instream', function () { + let bidRequests = [validBidSpaceInstream]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_640x480_0:640x480;1'); + expect(data.vctx).to.equal(1); + expect(data.vv).to.equal(3); + }); + + it('should return correct e parameter with support vast with one space with size default and vctx default', function () { + let bidRequests = [validBidSpaceVastNoContext]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_640x480_0:640x480;1'); + expect(data.vctx).to.equal(1); + expect(data.vv).to.equal(3); + }); + + it('if 2 bids arrive, one outstream and the other instream, instream has more priority', function () { + let bidRequests = [validBidSpaceOutstream, validBidSpaceInstream]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_640x480_0:640x480;1'); + expect(data.vctx).to.equal(1); + expect(data.vv).to.equal(3); + }); + it('if 2 bids arrive, one outstream and another banner, outstream has more priority', function () { + let bidRequests = [validBidSpaceOutstream, validBidSpaceName]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_300x600_0:300x600;1'); + expect(data.vctx).to.equal(2); + expect(data.vv).to.equal(3); + }); + + it('should return correct e parameter with support vast with one space outstream', function () { + let bidRequests = [validBidSpaceOutstream, validBidOutstreamNoSize]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.e).to.equal('video_300x600_0:300x600;1+video_640x480_1:640x480;1'); + expect(data.vctx).to.equal(2); + expect(data.vv).to.equal(3); + }); + it('should return correct e parameter with linear mapping attribute with more than one adunit', function () { let bidRequestsML = [validBidMappingLinear]; const NEW_CODE = ADUNIT_CODE + '2'; @@ -588,6 +838,40 @@ describe('E-Planning Adapter', function () { }; expect(bidResponse).to.deep.equal(expectedResponse); }); + + it('should correctly map the parameters in the response vast', function () { + const bidResponse = spec.interpretResponse(responseVast, { adUnitToBidId: { [CLEAN_ADUNIT_CODE_VAST]: BID_ID }, data: { vv: 2 } })[0]; + const expectedResponse = { + requestId: BID_ID, + cpm: CPM, + width: W, + height: H, + ttl: 120, + creativeId: CRID, + netRevenue: true, + currency: 'USD', + vastXml: ADM_VAST, + mediaType: VIDEO + }; + expect(bidResponse).to.deep.equal(expectedResponse); + }); + + it('should correctly map the parameters in the response vast vv 1', function () { + const bidResponse = spec.interpretResponse(responseVastVV1, { adUnitToBidId: { [CLEAN_ADUNIT_CODE_VAST]: BID_ID }, data: { vv: 1 } })[0]; + const expectedResponse = { + requestId: BID_ID, + cpm: CPM, + width: W, + height: H, + ttl: 120, + creativeId: CRID, + netRevenue: true, + currency: 'USD', + vastXml: ADM_VAST_VV_1, + mediaType: VIDEO + }; + expect(bidResponse).to.deep.equal(expectedResponse); + }); }); describe('getUserSyncs', function () { @@ -1029,7 +1313,7 @@ describe('E-Planning Adapter', function () { sandbox.restore(); }) - it('should add eids to the request ', function() { + it('should add eids to the request', function() { let bidRequests = [validBidView]; const expected_id5id = encodeURIComponent(JSON.stringify({ uid: 'ID5-ZHMOL_IfFSt7_lVYX8rBZc6GH3XMWyPQOBUfr4bm0g!', ext: { linkType: 1 } })); const request = spec.buildRequests(bidRequests, bidderRequest); diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 6db0b88a9bf..5789361d2a1 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -4,6 +4,7 @@ import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import {server} from 'test/mocks/xhr.js'; const CODE = 'feedad'; +const EXPECTED_ADAPTER_VERSION = '1.0.6'; describe('FeedAdAdapter', function () { describe('Public API', function () { @@ -300,6 +301,20 @@ describe('FeedAdAdapter', function () { expect(result.data.gdprApplies).to.equal(request.gdprConsent.gdprApplies); expect(result.data.consentIabTcf).to.equal(request.gdprConsent.consentString); }); + it('should include adapter and prebid version', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids[0].params.prebid_adapter_version).to.equal(EXPECTED_ADAPTER_VERSION); + expect(result.data.bids[0].params.prebid_sdk_version).to.equal('$prebid.version$'); + }); }); describe('interpretResponse', function () { @@ -358,75 +373,67 @@ describe('FeedAdAdapter', function () { const pixelSync2 = {type: 'image', url: 'the pixel url 2'}; const iFrameSync1 = {type: 'iframe', url: 'the iFrame url 1'}; const iFrameSync2 = {type: 'iframe', url: 'the iFrame url 2'}; - - it('should pass through the syncs out of the extension fields of the server response', function () { - const serverResponse = [{ + const response1 = { + body: [{ ext: { pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], + iframes: [iFrameSync1] + }, + }] + }; + const response2 = { + body: [{ + ext: { + pixels: [pixelSync1], + iframes: [iFrameSync1], } - }]; - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse) + }, { + ext: { + pixels: [pixelSync2], + iframes: [iFrameSync2], + } + }] + }; + it('should pass through the syncs out of the extension fields of the server response', function () { + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1]) expect(result).to.deep.equal([ pixelSync1, pixelSync2, iFrameSync1, - iFrameSync2, ]); }); it('should concat the syncs of all responses', function () { - const serverResponse = [{ - ext: { - pixels: [pixelSync1], - iframes: [iFrameSync2], - }, - ad: 'ad html', - cpm: 100 - }, { - ext: { - iframes: [iFrameSync1], - } - }, { - ext: { - pixels: [pixelSync2], - } - }]; - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1, response2]); expect(result).to.deep.equal([ pixelSync1, + pixelSync2, + iFrameSync1, iFrameSync2, + ]); + }); + + it('should concat the syncs of all bids', function () { + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response2]); + expect(result).to.deep.equal([ + pixelSync1, iFrameSync1, pixelSync2, + iFrameSync2, ]); }); it('should filter out duplicates', function () { - const serverResponse = [{ - ext: { - pixels: [pixelSync1, pixelSync1], - iframes: [iFrameSync2, iFrameSync2], - } - }, { - ext: { - iframes: [iFrameSync2, iFrameSync2], - } - }]; - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response1, response1]); expect(result).to.deep.equal([ pixelSync1, - iFrameSync2, + pixelSync2, + iFrameSync1, ]); }); it('should not include iFrame syncs if the option is disabled', function () { - const serverResponse = [{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]; - const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [response1]); expect(result).to.deep.equal([ pixelSync1, pixelSync2, @@ -434,27 +441,36 @@ describe('FeedAdAdapter', function () { }); it('should not include pixel syncs if the option is disabled', function () { - const serverResponse = [{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]; - const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [response1]); expect(result).to.deep.equal([ iFrameSync1, - iFrameSync2, ]); }); it('should not include any syncs if the sync options are disabled or missing', function () { - const serverResponse = [{ - ext: { - pixels: [pixelSync1, pixelSync2], - iframes: [iFrameSync1, iFrameSync2], - } - }]; - const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, serverResponse); + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}, [response1]); + expect(result).to.deep.equal([]); + }); + + it('should handle empty responses', function () { + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, []) + expect(result).to.deep.equal([]); + }); + + it('should not throw if the server response is weird', function () { + const responses = [ + {body: null}, + {body: 'null'}, + {body: 1234}, + {body: {}}, + {body: [{}, 123]}, + ]; + expect(() => spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, responses)).to.not.throw(); + }); + + it('should return empty array if the body extension is null', function () { + const response = {body: [{ext: null}]}; + const result = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [response]); expect(result).to.deep.equal([]); }); }); @@ -592,7 +608,8 @@ describe('FeedAdAdapter', function () { prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, - sdk_version: '1.0.3' + prebid_adapter_version: EXPECTED_ADAPTER_VERSION, + prebid_sdk_version: '$prebid.version$', }; subject(data); expect(server.requests.length).to.equal(1); diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index 8ef99727ce7..92a4b42f6f8 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -94,7 +94,12 @@ describe('fluctAdapter', function () { expect(request.data.params.kv).to.eql(undefined); }); - it('includes filtered user.eids if any exists', function () { + it('includes no data.schain by default', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.schain).to.eql(undefined); + }); + + it('includes filtered user.eids if any exist', function () { const bidRequests2 = bidRequests.map( (bidReq) => Object.assign(bidReq, { userIdAsEids: [ @@ -175,6 +180,37 @@ describe('fluctAdapter', function () { imsids: ['imsid1', 'imsid2'] }); }); + + it('includes data.schain if any exists', function () { + // this should be done by schain.js + const bidRequests2 = bidRequests.map( + (bidReq) => Object.assign(bidReq, { + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: 'publisher-id', + hp: 1 + } + ] + } + }) + ); + const request = spec.buildRequests(bidRequests2, bidderRequest)[0]; + expect(request.data.schain).to.eql({ + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: 'publisher-id', + hp: 1 + } + ] + }); + }); }); describe('interpretResponse', function() { diff --git a/test/spec/modules/fpdModule_spec.js b/test/spec/modules/fpdModule_spec.js index 8e95f462830..4536b304f9d 100644 --- a/test/spec/modules/fpdModule_spec.js +++ b/test/spec/modules/fpdModule_spec.js @@ -1,16 +1,14 @@ import {expect} from 'chai'; import {config} from 'src/config.js'; -import {getRefererInfo} from 'src/refererDetection.js'; -import {processFpd, registerSubmodules, startAuctionHook, reset} from 'modules/fpdModule/index.js'; -import * as enrichmentModule from 'modules/enrichmentFpdModule.js'; -import * as validationModule from 'modules/validationFpdModule/index.js'; -import {resetEnrichments} from 'modules/enrichmentFpdModule.js'; -import {GreedyPromise} from '../../../src/utils/promise.js'; +import {registerSubmodules, reset, startAuctionHook} from 'modules/fpdModule/index.js'; describe('the first party data module', function () { afterEach(function () { config.resetConfig(); }); + after(() => { + reset(); + }) describe('startAuctionHook', () => { const mockFpd = { @@ -50,282 +48,4 @@ describe('the first party data module', function () { }); }); }); - - describe('first party data intitializing', function () { - let width; - let widthStub; - let height; - let heightStub; - let querySelectorStub; - let canonical; - let keywords; - let lowEntropySuaStub; - let highEntropySuaStub; - - before(function() { - reset(); - registerSubmodules(enrichmentModule); - registerSubmodules(validationModule); - - canonical = document.createElement('link'); - canonical.rel = 'canonical'; - keywords = document.createElement('meta'); - keywords.name = 'keywords'; - }); - - beforeEach(function() { - resetEnrichments(); - querySelectorStub = sinon.stub(window.top.document, 'querySelector'); - querySelectorStub.withArgs("link[rel='canonical']").returns(canonical); - querySelectorStub.withArgs("meta[name='keywords']").returns(keywords); - widthStub = sinon.stub(window.top, 'innerWidth').get(function () { - return width; - }); - heightStub = sinon.stub(window.top, 'innerHeight').get(function () { - return height; - }); - lowEntropySuaStub = sinon.stub(enrichmentModule.sua, 'le').callsFake(() => null); - highEntropySuaStub = sinon.stub(enrichmentModule.sua, 'he').callsFake(() => GreedyPromise.resolve()); - }); - - afterEach(function() { - widthStub.restore(); - heightStub.restore(); - querySelectorStub.restore(); - canonical = document.createElement('link'); - canonical.rel = 'canonical'; - keywords = document.createElement('meta'); - keywords.name = 'keywords'; - lowEntropySuaStub.restore(); - highEntropySuaStub.restore(); - }); - - it('filters ortb2 data that is set', function () { - const global = { - user: { - data: {}, - gender: 'f', - age: 45 - }, - site: { - content: { - data: [{ - segment: { - test: 1 - }, - name: 'foo' - }, { - segment: [{ - id: 'test' - }, { - id: 3 - }], - name: 'bar' - }] - } - }, - device: { - w: 1, - h: 1 - } - }; - - canonical.href = 'https://www.domain.com/path?query=12345'; - width = 1120; - height = 750; - - return processFpd({global}).then(({global: validated}) => { - expect(validated.site.ref).to.equal(getRefererInfo().ref || undefined); - expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); - expect(validated.site.domain).to.equal('domain.com'); - expect(validated.site.content.data).to.deep.equal([{segment: [{id: 'test'}], name: 'bar'}]); - expect(validated.user.data).to.be.undefined; - expect(validated.device).to.deep.to.equal({w: 1, h: 1}); - expect(validated.site.keywords).to.be.undefined; - }); - }); - - it('should not overwrite existing data with default settings', function () { - const global = { - site: { - ref: 'https://referer.com' - } - }; - - return processFpd({global}).then(({global: validated}) => { - expect(validated.site.ref).to.equal('https://referer.com'); - }); - }); - - it('should allow overwrite default data with setConfig', function () { - const global = { - site: { - ref: 'https://referer.com' - } - }; - - return processFpd({global}).then(({global: validated}) => { - expect(validated.site.ref).to.equal('https://referer.com'); - }); - }); - - it('should filter all data', function () { - let global = { - imp: [], - site: { - name: 123, - domain: 456, - page: 789, - ref: 987, - keywords: ['keywords'], - search: 654, - cat: 'cat', - sectioncat: 'sectioncat', - pagecat: 'pagecat', - content: { - data: [{ - name: 1, - segment: [] - }] - } - }, - user: { - yob: 'twenty', - gender: 0, - keywords: ['foobar'], - data: ['test'] - }, - device: [800, 450], - cur: { - adServerCurrency: 'USD' - } - }; - config.setConfig({'firstPartyData': {skipEnrichments: true}}); - return processFpd({global}).then(({global: validated}) => { - expect(validated).to.deep.equal({}); - }); - }); - - it('should add enrichments but not alter any arbitrary ortb2 data', function () { - let global = { - site: { - ext: { - data: { - inventory: ['value1'] - } - } - }, - user: { - ext: { - data: { - visitor: ['value2'] - } - } - }, - cur: ['USD'] - }; - return processFpd({global}).then(({global: validated}) => { - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.ext.data).to.deep.equal({inventory: ['value1']}); - expect(validated.user.ext.data).to.deep.equal({visitor: ['value2']}); - expect(validated.cur).to.deep.equal(['USD']); - }) - }); - - it('should filter bidderConfig data', function () { - let bidder = { - bidderA: { - site: { - keywords: 'other', - ref: 'https://domain.com' - }, - user: { - keywords: 'test', - data: [{ - segment: [{id: 4}], - name: 't' - }] - } - } - }; - - return processFpd({bidder}).then(({bidder: validated}) => { - expect(validated.bidderA).to.not.be.undefined; - expect(validated.bidderA.user.data).to.be.undefined; - expect(validated.bidderA.user.keywords).to.equal('test'); - expect(validated.bidderA.site.keywords).to.equal('other'); - expect(validated.bidderA.site.ref).to.equal('https://domain.com'); - }) - }); - - it('should not filter bidderConfig data as it is valid', function () { - let bidder = { - bidderA: { - site: { - keywords: 'other', - ref: 'https://domain.com' - }, - user: { - keywords: 'test', - data: [{ - segment: [{id: 'data1_id'}], - name: 'data1' - }] - } - } - }; - - return processFpd({bidder}).then(({bidder: validated}) => { - expect(validated.bidderA).to.not.be.undefined; - expect(validated.bidderA.user.data).to.deep.equal([{segment: [{id: 'data1_id'}], name: 'data1'}]); - expect(validated.bidderA.user.keywords).to.equal('test'); - expect(validated.bidderA.site.keywords).to.equal('other'); - expect(validated.bidderA.site.ref).to.equal('https://domain.com'); - }); - }); - - it('should not set default values if skipEnrichments is turned on', function () { - config.setConfig({'firstPartyData': {skipEnrichments: true}}); - - let global = { - site: { - keywords: 'other' - }, - user: { - keywords: 'test', - data: [{ - segment: [{id: 'data1_id'}], - name: 'data1' - }] - } - }; - - return processFpd({global}).then(({global: validated}) => { - expect(validated.device).to.be.undefined; - expect(validated.site.ref).to.be.undefined; - expect(validated.site.page).to.be.undefined; - expect(validated.site.domain).to.be.undefined; - }); - }); - - it('should not validate ortb2 data if skipValidations is turned on', function () { - config.setConfig({'firstPartyData': {skipValidations: true}}); - - let global = { - site: { - keywords: 'other' - }, - user: { - keywords: 'test', - data: [{ - segment: [{id: 'nonfiltered'}] - }] - } - }; - - return processFpd({global}).then(({global: validated}) => { - expect(validated.user.data).to.deep.equal([{segment: [{id: 'nonfiltered'}]}]); - }); - }); - }); }); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 81f4ecec074..d1e0b055239 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -128,7 +128,7 @@ describe('freewheelSSP BidAdapter Test', () => { it('should return a properly formatted request with schain defined', function () { const request = spec.buildRequests(bidRequests); const payload = request[0].data; - expect(payload.schain).to.deep.equal(bidRequests[0].schain) + expect(payload.schain).to.deep.equal('{\"ver\":\"1.0\",\"complete\":1,\"nodes\":[{\"asi\":\"example.com\",\"sid\":\"0\",\"hp\":1,\"rid\":\"bidrequestid\",\"domain\":\"example.com\"}]}'); }); it('sends bid request to ENDPOINT via GET', () => { @@ -483,6 +483,23 @@ describe('freewheelSSP BidAdapter Test', () => { 'bidId': '2', 'bidderRequestId': '3', 'auctionId': '4', + }, + { + 'bidder': 'freewheelssp', + 'params': { + 'zoneId': '277225', + 'format': 'test' + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'playerSize': [300, 600], + } + }, + 'sizes': [[300, 400]], + 'bidId': '2', + 'bidderRequestId': '3', + 'auctionId': '4', } ]; @@ -510,7 +527,8 @@ describe('freewheelSSP BidAdapter Test', () => { ' ' + ' ' + ' 0.2000' + - ' ' + + ' ' + + ' ' + ' ' + ' ' + ''; @@ -536,12 +554,15 @@ describe('freewheelSSP BidAdapter Test', () => { bannerId: '12345', vastXml: response, mediaType: 'video', - ad: ad + ad: ad, + meta: { + advertiserDomains: 'minotaur.com' + } } ]; let result = spec.interpretResponse(response, request[0]); - expect(result[0].meta.advertiserDomains).to.deep.equal([]); + expect(result[0].meta.advertiserDomains).to.deep.equal(['minotaur.com']); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); expect(result[0].campaignId).to.equal('SMF-WOW-55555'); expect(result[0].bannerId).to.equal('12345'); @@ -570,7 +591,7 @@ describe('freewheelSSP BidAdapter Test', () => { ]; let result = spec.interpretResponse(response, request[0]); - expect(result[0].meta.advertiserDomains).to.deep.equal([]); + expect(result[0].meta.advertiserDomains).to.deep.equal(['minotaur.com']); expect(result[0].dealId).to.equal('NRJ-PRO-00008'); expect(result[0].campaignId).to.equal('SMF-WOW-55555'); expect(result[0].bannerId).to.equal('12345'); diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index 2c3f3f8576c..ecd610a12fb 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -275,10 +275,55 @@ describe('FTRACK ID System', () => { describe(`decode() method`, () => { it(`should respond with an object with the key 'ftrackId'`, () => { - expect(ftrackIdSubmodule.decode('value', configMock)).to.deep.equal({ + const MOCK_VALUE_STRINGS = { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + }, + MOCK_VALUE_ARRAYS = { + HHID: ['household_test_id', 'a', 'b'], + DeviceID: ['device_test_id', 'c', 'd'], + SingleDeviceID: ['single_device_test_id', 'e', 'f'] + }, + MOCK_VALUE_BOTH = { + foo: ['foo', 'a', 'b'], + bar: 'bar', + baz: ['baz', 'baz', 'baz'] + }; + + // strings are just passed through + expect(ftrackIdSubmodule.decode(MOCK_VALUE_STRINGS, configMock)).to.deep.equal({ + ftrackId: { + ext: { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + }, + uid: 'device_test_id', + }, + }); + + // arrays are converted to strings + expect(ftrackIdSubmodule.decode(MOCK_VALUE_ARRAYS, configMock)).to.deep.equal({ + ftrackId: { + ext: { + HHID: 'household_test_id|a|b', + DeviceID: 'device_test_id|c|d', + SingleDeviceID: 'single_device_test_id|e|f' + }, + uid: 'device_test_id|c|d', + }, + }); + + // mix of both but uid should be empty string because DeviceId is not defined + expect(ftrackIdSubmodule.decode(MOCK_VALUE_BOTH, configMock)).to.deep.equal({ ftrackId: { - ext: { 0: 'v', 1: 'a', 2: 'l', 3: 'u', 4: 'e' }, - uid: undefined, + ext: { + foo: 'foo|a|b', + bar: 'bar', + baz: 'baz|baz|baz' + }, + uid: '', }, }); }); @@ -309,56 +354,40 @@ describe('FTRACK ID System', () => { }); describe('pbjs "get id" methods', () => { - // The full set of ftrack IDs to test against - let expectedIds = { - HHID: ['household_test_id'], - DeviceID: ['device_test_id'], - SingleDeviceID: ['single_device_test_id'] - }; - // The full config mock - let userSyncConfigMock = { - userSync: { - auctionDelay: 10, - userIds: [{ - name: 'ftrack', - value: { - ftrackId: expectedIds - } - }] - } - }; - // The full eids response - let expectedEids = [{ - id: 'device_test_id', - atype: 1, - ext: { - HHID: expectedIds.HHID.join('|'), - DeviceID: expectedIds.DeviceID.join('|'), - SingleDeviceID: expectedIds.SingleDeviceID.join('|') - } - }]; - beforeEach(() => { init(config); setSubmoduleRegistry([ftrackIdSubmodule]); }); - afterEach(() => { - // Reset expectedIds to the default values because some tests overwrite them - expectedIds = { - HHID: ['household_test_id'], - DeviceID: ['device_test_id'], - SingleDeviceID: ['single_device_test_id'] - }; - }); - describe('pbjs.getUserIdsAsync()', () => { - it('should return the IDs in the correct schema', () => { - config.setConfig(userSyncConfigMock); + it('should return the IDs in the correct schema - flat schema', () => { + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'ftrack', + value: { + ftrackId: { + uid: 'device_test_id', + ext: { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + } + } + } + }] + } + }); getGlobal().getUserIdsAsync().then(ids => { expect(ids).to.deep.equal({ - ftrackId: expectedIds + uid: 'device_test_id', + ftrackId: { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + } }); }); }); @@ -366,73 +395,166 @@ describe('FTRACK ID System', () => { describe('pbjs.getUserIds()', () => { it('should return the IDs in the correct schema', () => { - config.setConfig(userSyncConfigMock); + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'ftrack', + value: { + ftrackId: { + uid: 'device_test_id', + ext: { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + } + } + } + }] + } + }); expect(getGlobal().getUserIds()).to.deep.equal({ - 'ftrackId': expectedIds + ftrackId: { + uid: 'device_test_id', + ext: { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + } + } }); }); }); describe('pbjs.getUserIdsAsEids()', () => { - it('should return the correct EIDs schema', () => { - let userSyncConfig = Object.assign({}, userSyncConfigMock); - - config.setConfig(userSyncConfig); + it('should return the correct EIDs schema ', () => { + // Pass all three IDs + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'ftrack', + value: { + ftrackId: { + uid: 'device_test_id', + ext: { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + } + } + } + }] + } + }); - expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEids); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ + source: 'flashtalking.com', + uids: [{ + id: 'device_test_id', + atype: 1, + ext: { + HHID: 'household_test_id', + DeviceID: 'device_test_id', + SingleDeviceID: 'single_device_test_id' + } + }] + }]); }); describe('by ID type:', () => { it('- DeviceID', () => { - let userSyncConfig = Object.assign({}, userSyncConfigMock); - - userSyncConfig.userSync.userIds[0].value.ftrackId = { - DeviceID: ['device_test_id'] - }; - - let expectedEidsClone = expectedEids.slice(); - expectedEidsClone[0].ext = { - DeviceID: expectedIds.DeviceID.join('|') - }; - - config.setConfig(userSyncConfig); + // Pass DeviceID only + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'ftrack', + value: { + ftrackId: { + uid: 'device_test_id', + ext: { + DeviceID: 'device_test_id', + } + } + } + }] + } + }); - expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEidsClone); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ + source: 'flashtalking.com', + uids: [{ + id: 'device_test_id', + atype: 1, + ext: { + DeviceID: 'device_test_id' + } + }] + }]); }); it('- HHID', () => { - let userSyncConfig = Object.assign({}, userSyncConfigMock); - userSyncConfig.userSync.userIds[0].value.ftrackId = { - HHID: ['household_test_id'] - }; - - let expectedEidsClone = expectedEids.slice(); - expectedEidsClone[0].id = ''; - expectedEidsClone[0].ext = { - HHID: expectedIds.HHID.join('|') - }; - - config.setConfig(userSyncConfig); + // Pass HHID only + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'ftrack', + value: { + ftrackId: { + uid: '', + ext: { + HHID: 'household_test_id' + } + } + } + }] + } + }); - expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEidsClone); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ + source: 'flashtalking.com', + uids: [{ + id: '', + atype: 1, + ext: { + HHID: 'household_test_id' + } + }] + }]); }); it('- SingleDeviceID', () => { - let userSyncConfig = Object.assign({}, userSyncConfigMock); - userSyncConfig.userSync.userIds[0].value.ftrackId = { - SingleDeviceID: ['single_device_test_id'] - }; - - let expectedEidsClone = expectedEids.slice(); - expectedEidsClone[0].id = ''; - expectedEidsClone[0].ext = { - SingleDeviceID: expectedIds.SingleDeviceID.join('|') - }; - - config.setConfig(userSyncConfig); + // Pass SingleDeviceID only + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'ftrack', + value: { + ftrackId: { + uid: '', + ext: { + SingleDeviceID: 'single_device_test_id' + } + } + } + }] + } + }); - expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEidsClone); + expect(getGlobal().getUserIdsAsEids()).to.deep.equal([{ + source: 'flashtalking.com', + uids: [{ + id: '', + atype: 1, + ext: { + SingleDeviceID: 'single_device_test_id' + } + }] + }]); }); }); }); diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index ddd522decab..8d58990bb66 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -20,6 +20,7 @@ import * as events from 'src/events.js'; import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry import 'src/prebid.js' import {hook} from '../../../src/hook.js'; +import {VENDORLESS_GVLID} from '../../../src/consentHandler.js'; describe('gdpr enforcement', function () { let nextFnSpy; @@ -150,13 +151,13 @@ describe('gdpr enforcement', function () { } }); - deviceAccessHook(nextFnSpy, false); + deviceAccessHook(nextFnSpy); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: false } - sinon.assert.calledWith(nextFnSpy, false, undefined, undefined, result); + sinon.assert.calledWith(nextFnSpy, undefined, undefined, result); }); it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { @@ -178,8 +179,8 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, false, 5, 'rubicon'); + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, 5, 'rubicon'); expect(logWarnSpy.callCount).to.equal(0); }); @@ -201,8 +202,8 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, false, 3, 'rubicon'); + deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, 3, 'rubicon'); expect(logWarnSpy.callCount).to.equal(1); }); @@ -224,13 +225,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, 1, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, false, 1, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', result); }); it('should use gvlMapping set by publisher', function() { @@ -255,13 +256,13 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, 1, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, false, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); config.resetConfig(); }); @@ -290,31 +291,17 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, 1, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, false, 4, 'appnexus', result); + sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', result); config.resetConfig(); curBidderStub.restore(); }); - it(`should mark module as vendorless for rule validation when isVendorless = true and ${STRICT_STORAGE_ENFORCEMENT} is set`, () => { - setEnforcementConfig({ - [STRICT_STORAGE_ENFORCEMENT]: true - }); - let consentData = { - vendorData: staticConfig.consentData.getTCData, - gdprApplies: true - } - gdprDataHandlerStub.returns(consentData); - const validate = sinon.stub().callsFake(() => true); - deviceAccessHook(nextFnSpy, true, 123, 'mockModule', undefined, {validate}); - expect(validate.args[0][4]('mockModule')).to.be.true; - }); - it(`should not enforce consent for vendorless modules if ${STRICT_STORAGE_ENFORCEMENT} is not set`, () => { setEnforcementConfig({}); let consentData = { @@ -323,9 +310,9 @@ describe('gdpr enforcement', function () { } gdprDataHandlerStub.returns(consentData); const validate = sinon.stub().callsFake(() => false); - deviceAccessHook(nextFnSpy, true, 123, 'mockModule', undefined, {validate}); + deviceAccessHook(nextFnSpy, VENDORLESS_GVLID, 'mockModule', undefined, {validate}); sinon.assert.callCount(validate, 0); - sinon.assert.calledWith(nextFnSpy, true, 123, 'mockModule', {hasEnforcementHook: true, valid: true}); + sinon.assert.calledWith(nextFnSpy, VENDORLESS_GVLID, 'mockModule', {hasEnforcementHook: true, valid: true}); }) }); @@ -804,11 +791,12 @@ describe('gdpr enforcement', function () { }); describe('validateRules', function () { - const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = []) => ({ + const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = [], softVendorExceptions = []) => ({ purpose: purposeName, - enforcePurpose: enforcePurpose, - enforceVendor: enforceVendor, - vendorExceptions: vendorExceptions + enforcePurpose, + enforceVendor, + vendorExceptions, + softVendorExceptions, }); const consentData = { @@ -922,6 +910,19 @@ describe('gdpr enforcement', function () { expect(isAllowed).to.equal(true); }); + describe('when the vendor has a softVendorException', () => { + const gdprRule = createGdprRule('storage', true, true, [], [vendorBlockedModule]); + + it('should return false if general consent was not given', () => { + const isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.be.false; + }) + it('should return true if general consent was given', () => { + const isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId); + expect(isAllowed).to.be.true; + }) + }) + describe('when module does not need vendor consent', () => { Object.entries({ 'storage': 1, @@ -937,11 +938,9 @@ describe('gdpr enforcement', function () { it(`should be ${t} when purpose is ${t}`, () => { const consent = utils.deepClone(consentData); consent.vendorData.purpose.consents[purposeNo] = consentGiven; - // vendor consent (and gvlid) should be ignored - consent.vendorData.vendor.consents[123] = !consentGiven; // take legitimate interest out of the picture for this test consent.vendorData.purpose.legitimateInterests = {}; - const actual = validateRules(rule, consent, 'mockModule', 123, () => true); + const actual = validateRules(rule, consent, 'mockModule', VENDORLESS_GVLID); expect(actual).to.equal(consentGiven); }) }) @@ -1131,56 +1130,104 @@ describe('gdpr enforcement', function () { }) }); - describe('getGvlid', function() { + describe('gvlid resolution', () => { let sandbox; - let getGvlidForBidAdapterStub; - let getGvlidForUserIdModuleStub; - let getGvlidForAnalyticsAdapterStub; beforeEach(function() { sandbox = sinon.createSandbox(); - getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); - getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); - getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); }); + afterEach(function() { sandbox.restore(); config.resetConfig(); }); - it('should return "null" if called without passing any argument', function() { - const gvlid = getGvlid(); - expect(gvlid).to.equal(null); - }); + describe('getGvlid', function() { + let getGvlidForBidAdapterStub; + let getGvlidForUserIdModuleStub; + let getGvlidForAnalyticsAdapterStub; + beforeEach(function() { + getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); + getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); + getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); + }); - it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); + it('should return "null" if called without passing any argument', function() { + const gvlid = getGvlid(); + expect(gvlid).to.equal(null); + }); - const gvlid = getGvlid('moduleA'); - expect(gvlid).to.equal(null); - }); + it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { + getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); + getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); + getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); - it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { - config.setConfig({ - gvlMapping: { - moduleA: 1 - } + const gvlid = getGvlid('moduleA'); + expect(gvlid).to.equal(null); + }); + + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { + config.setConfig({ + gvlMapping: { + moduleA: 1 + } + }); + + // Actual GVL ID for moduleA is 2, as defined on its the bidAdapter.js file. + getGvlidForBidAdapterStub.withArgs('moduleA').returns(2); + + const gvlid = getGvlid('moduleA'); + expect(gvlid).to.equal(1); }); - // Actual GVL ID for moduleA is 2, as defined on its the bidAdapter.js file. - getGvlidForBidAdapterStub.withArgs('moduleA').returns(2); + it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { + getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); + getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); + getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); + + expect(getGvlid('moduleA')).to.equal(7); + }); - const gvlid = getGvlid('moduleA'); - expect(gvlid).to.equal(1); + it('should pass extra arguments to analytics\' getGvlid', () => { + getGvlidForAnalyticsAdapterStub.withArgs('analytics').returns(321); + const cfg = {some: 'args'}; + getGvlid('analytics', cfg); + sinon.assert.calledWith(getGvlidForAnalyticsAdapterStub, 'analytics', cfg); + }); }); - it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { - getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); - getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); - getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); + describe('getGvlidForAnalyticsAdapter', () => { + let getAnalyticsAdapter, adapter, adapterEntry; + + beforeEach(() => { + adapter = {}; + adapterEntry = { + adapter + }; + getAnalyticsAdapter = sandbox.stub(adapterManager, 'getAnalyticsAdapter'); + getAnalyticsAdapter.withArgs('analytics').returns(adapterEntry); + }); + + it('should return gvlid from adapterManager if defined', () => { + adapterEntry.gvlid = 123; + adapter.gvlid = 321 + expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(123); + }); + + it('should return gvlid from adapter if defined', () => { + adapter.gvlid = 321; + expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.equal(321); + }); - expect(getGvlid('moduleA')).to.equal(7); + it('should invoke adapter.gvlid if it\'s a function', () => { + adapter.gvlid = (cfg) => cfg.k + const cfg = {k: 231}; + expect(internal.getGvlidForAnalyticsAdapter('analytics', cfg)).to.eql(231); + }); + + it('should not choke if adapter gvlid fn throws', () => { + adapter.gvlid = () => { throw new Error(); }; + expect(internal.getGvlidForAnalyticsAdapter('analytics')).to.not.be.ok; + }); }); - }); + }) }); diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..a5a6074c425 --- /dev/null +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -0,0 +1,284 @@ +import {defaultHandler, GenericAnalytics} from '../../../modules/genericAnalyticsAdapter.js'; +import * as events from 'src/events.js'; +import * as CONSTANTS from 'src/constants.json'; + +const {AUCTION_INIT, BID_RESPONSE} = CONSTANTS.EVENTS; + +describe('Generic analytics', () => { + describe('adapter', () => { + let adapter, sandbox, clock; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(events, 'getEvents').returns([]); + clock = sandbox.useFakeTimers(); + adapter = new GenericAnalytics(); + }); + + afterEach(() => { + adapter.disableAnalytics(); + sandbox.restore(); + }); + + describe('configuration', () => { + it('should be accepted if valid', () => { + adapter.enableAnalytics({ + options: { + url: 'mock', + method: 'GET', + batchSize: 123, + batchDelay: 321 + } + }); + expect(adapter.enabled).to.be.true; + }); + + describe('should not work if', () => { + afterEach(function() { + expect(adapter.enabled).to.equal(false, this.currentTest.title); + }); + + it('neither handler nor url are specified', () => { + adapter.enableAnalytics({}); + }); + + ['batchSize', 'batchDelay', 'handler'].forEach(option => { + it(`${option} is not valid`, () => { + adapter.enableAnalytics({ + options: { + url: 'mock', + [option]: false + } + }); + }); + }); + + it('method is not GET or POST', () => { + adapter.enableAnalytics({ + options: { + url: 'mock', + method: 'PATCH' + } + }); + }); + + it('events is not an object', () => { + adapter.enableAnalytics({ + options: { + url: 'mock', + events: null + } + }); + }); + + it('events\' properties are not functions', () => { + adapter.enableAnalytics({ + options: { + url: 'mock', + events: { + bidResponse: null + } + } + }); + }); + }); + }); + + describe('when handler is specified', () => { + let handler; + beforeEach(() => { + handler = sinon.stub(); + }); + + it('should collect events in batches, and call handler', () => { + adapter.enableAnalytics({ + options: { + handler, + batchSize: 2 + } + }); + events.emit(AUCTION_INIT, {i: 0}); + sinon.assert.notCalled(handler); + events.emit(BID_RESPONSE, {i: 0}); + sinon.assert.calledWith(handler, sinon.match((arg) => { + return sinon.match({eventType: AUCTION_INIT, args: {i: 0}}).test(arg[0]) && + sinon.match({eventType: BID_RESPONSE, args: {i: 0}}).test(arg[1]); + })); + }); + + it('should not choke if handler throws', () => { + adapter.enableAnalytics({ + options: { + handler, + batchSize: 1 + } + }); + handler.throws(new Error()); + events.emit(AUCTION_INIT, {i: 0}); + let recv; + handler.reset(); + handler.callsFake((arg) => { + recv = arg; + }); + events.emit(BID_RESPONSE, {i: 1}); + expect(recv).to.eql([{eventType: BID_RESPONSE, args: {i: 1}}]); + }); + + it('should not cause infinite recursion, if handler triggers more events', () => { + let i = 0; + handler.callsFake(() => { + if (i <= 100) { + i++; + events.emit(BID_RESPONSE, {}); + } + }); + adapter.enableAnalytics({ + options: { + handler, + } + }); + events.emit(AUCTION_INIT, {}); + expect(i >= 100).to.be.false; + }); + + it('should send incomplete batch after batchDelay', () => { + adapter.enableAnalytics({ + options: { + batchDelay: 100, + batchSize: 2, + handler + } + }); + [0, 1, 2].forEach(i => events.emit(BID_RESPONSE, {i})); + sinon.assert.calledOnce(handler); + clock.tick(100); + sinon.assert.calledTwice(handler); + }); + + it('does not send empty batches', () => { + adapter.enableAnalytics({ + options: { + batchDelay: 100, + batchSize: 2, + handler + } + }); + [0, 1, 2].forEach(i => events.emit(BID_RESPONSE, {i})); + sinon.assert.calledOnce(handler); + clock.tick(50); + events.emit(BID_RESPONSE, {i: 3}); + sinon.assert.calledTwice(handler); + clock.tick(100); + sinon.assert.calledTwice(handler); + }); + + describe('and options.events is specified', () => { + it('filters out other events', () => { + adapter.enableAnalytics({ + options: { + handler, + events: { + bidResponse(bid) { + return bid; + } + } + } + }); + events.emit(AUCTION_INIT, {}); + sinon.assert.notCalled(handler); + }); + + it('transforms event data', () => { + adapter.enableAnalytics({ + options: { + handler, + events: { + bidResponse(bid) { + return { + extra: 'data', + prop: bid.prop + } + } + } + } + }); + events.emit(BID_RESPONSE, {prop: 'value', i: 0}); + sinon.assert.calledWith(handler, sinon.match(data => sinon.match({extra: 'data', prop: 'value'}).test(data[0]))); + }); + + it('does not choke if an event handler throws', () => { + adapter.enableAnalytics({ + options: { + handler, + events: { + bidResponse(bid) { + return bid; + }, + auctionInit(auction) { + throw new Error(); + } + } + } + }); + events.emit(AUCTION_INIT, {}); + events.emit(BID_RESPONSE, {i: 0}); + sinon.assert.calledOnce(handler); + sinon.assert.calledWith(handler, sinon.match(data => sinon.match({i: 0}).test(data[0]))); + }); + + it('filters out events when their handler returns undefined', () => { + adapter.enableAnalytics({ + options: { + handler, + events: { + auctionInit(auction) { + return auction; + }, + bidResponse(bid) {} + } + } + }); + events.emit(AUCTION_INIT, {i: 0}); + events.emit(BID_RESPONSE, {i: 1}); + sinon.assert.calledOnce(handler); + sinon.assert.calledWith(handler, sinon.match(data => sinon.match({i: 0}).test(data[0]))); + }); + }); + }); + }); + + describe('default handler', () => { + const url = 'mock-url'; + + let ajax; + beforeEach(() => { + ajax = sinon.stub(); + }); + + Object.entries({ + 'GET': (data) => JSON.parse(data.data), + 'POST': (data) => JSON.parse(data) + }).forEach(([method, parse]) => { + describe(`when HTTP method is ${method}`, () => { + it('should send single event when batchSize is 1', () => { + const handler = defaultHandler({url, method, batchSize: 1, ajax}); + const payload = {i: 0}; + handler([payload, {}]); + sinon.assert.calledWith(ajax, url, sinon.match.any, + sinon.match(data => sinon.match(payload).test(parse(data))), + {method} + ); + }); + + it('should send multiple events when batchSize is greater than 1', () => { + const handler = defaultHandler({url, method, batchSize: 10, ajax}); + const payload = [{i: 0}, {i: 1}]; + handler(payload); + sinon.assert.calledWith(ajax, url, sinon.match.any, + sinon.match(data => sinon.match(payload).test(parse(data))), + {method} + ); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index cf4e0b53fde..eec1feff87a 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -2,6 +2,8 @@ import * as utils from '../../../src/utils.js'; import * as hook from '../../../src/hook.js' import { beforeInit, geoedgeSubmodule, setWrapper, wrapper, htmlPlaceholder, WRAPPER_URL, getClientUrl } from '../../../modules/geoedgeRtdProvider.js'; import { server } from '../../../test/mocks/xhr.js'; +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; let key = '123123123'; function makeConfig() { @@ -88,6 +90,19 @@ describe('Geoedge RTD module', function () { let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href === getClientUrl(key); expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); }); + it('should emit billable events with applicable winning bids', function () { + let applicableBid = mockBid('bidderA'); + let nonApplicableBid = mockBid('bidderB'); + let counter = 0; + events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, function (event) { + if (event.vendor === 'geoedge' && event.type === 'impression') { + counter += 1; + } + }); + events.emit(CONSTANTS.EVENTS.BID_WON, applicableBid); + events.emit(CONSTANTS.EVENTS.BID_WON, nonApplicableBid); + expect(counter).to.equal(1); + }); }); describe('onBidResponseEvent', function () { let bidFromA = mockBid('bidderA'); diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js new file mode 100644 index 00000000000..3795f3038a7 --- /dev/null +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -0,0 +1,398 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/globalsunBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'globalsun' + +describe('GlobalsunBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://endpoint.globalsun.io/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.globalsun.io/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.globalsun.io/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/glomexBidAdapter_spec.js b/test/spec/modules/glomexBidAdapter_spec.js index 30157da858b..8c53db8d605 100644 --- a/test/spec/modules/glomexBidAdapter_spec.js +++ b/test/spec/modules/glomexBidAdapter_spec.js @@ -49,6 +49,10 @@ const RESPONSE = { describe('glomexBidAdapter', function () { const adapter = newBidder(spec) + it('should expose gvlid', function() { + expect(spec.gvlid).to.equal(967) + }); + describe('inherited functions', function () { it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function') diff --git a/test/spec/modules/gravitoIdSystem_spec.js b/test/spec/modules/gravitoIdSystem_spec.js index e904355f7f1..9584f60c81d 100644 --- a/test/spec/modules/gravitoIdSystem_spec.js +++ b/test/spec/modules/gravitoIdSystem_spec.js @@ -40,7 +40,7 @@ describe('gravitompId module', function () { describe('decode()', function () { it('should return the gravitompId when it exists in cookie', function () { const decoded = gravitoIdSystemSubmodule.decode(GRAVITOID_TEST_OBJ); - expect(decoded).to.be.deep.equal({gravitompId: {id: GRAVITOID_TEST_VALUE}}); + expect(decoded).to.be.deep.equal({gravitompId: GRAVITOID_TEST_VALUE}); }); it('should return the undefined when decode id is not "string"', function () { diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index f98ca683024..d411f33ab50 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -44,8 +44,9 @@ describe('TheMediaGrid Adapter', function () { return JSON.parse(data); } const bidderRequest = { - refererInfo: {page: 'https://example.com'}, + refererInfo: { page: 'https://example.com' }, bidderRequestId: '22edbae2733bf6', + transactionId: '1239bd74-4511-4335-af21-e828852e25d7', auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', timeout: 3000 }; @@ -67,6 +68,7 @@ describe('TheMediaGrid Adapter', function () { 'bidId': '42dbe3a7168a6a', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + transactionId: '1239bd74-4511-4335-af21-e828852e25d7', }, { 'bidder': 'grid', @@ -78,6 +80,7 @@ describe('TheMediaGrid Adapter', function () { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + transactionId: '1239bd74-4511-4335-af21-e828852e25d7', }, { 'bidder': 'grid', @@ -95,6 +98,7 @@ describe('TheMediaGrid Adapter', function () { 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + transactionId: '1239bd74-4511-4335-af21-e828852e25d7', }, { 'bidder': 'grid', @@ -115,6 +119,7 @@ describe('TheMediaGrid Adapter', function () { 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', + transactionId: '1239bd74-4511-4335-af21-e828852e25d7', } ]; @@ -395,6 +400,158 @@ describe('TheMediaGrid Adapter', function () { getDataFromLocalStorageStub.restore(); }); + it('should send additional request with adUnits with withCriteo parameter', function () { + const fpdUserIdVal = '0b0f84a1-1596-4165-9742-2e1a7dfac57f'; + const getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').callsFake( + arg => arg === 'tmguid' ? fpdUserIdVal : null); + + const bidRequestsWithCriteo = bidRequests.map((bid, i) => + i % 2 ? bid : { + ...bid, + params: { + ...bid.params, + withCriteo: true, + networkId: 123 + i + }, + mediaTypes: { + ...bid.mediaTypes, + ...(bid.mediaTypes.video && { video: { + ...bid.mediaTypes.video, + context: 'instream', + protocols: [1, 2, 3], + maxduration: 30, + api: [1, 2], + skip: 1, + placement: 1, + minduration: 0, + playbackmethod: 1, + startdelay: 0 + } + }) + } + }); + + const [request, criteoRequest] = spec.buildRequests(bidRequestsWithCriteo, bidderRequest); + expect(request.data).to.be.an('string'); + expect(criteoRequest.data).to.be.an('object'); + const payload = parseRequest(request.data); + const criteoPayload = criteoRequest.data; + expect(request.url).to.equal('https://grid.bidswitch.net/hbjson'); + expect(criteoRequest.url.replace(/\?.*$/, '')) + .to.equal('https://bidder.criteo.com/cdb'); + expect(payload).to.deep.equal({ + 'id': bidderRequest.bidderRequestId, + 'site': { + 'page': referrer + }, + 'tmax': bidderRequest.timeout, + 'source': { + 'tid': bidderRequest.auctionId, + 'ext': {'wrapper': 'Prebid_js', 'wrapper_version': '$prebid.version$'} + }, + 'user': { + 'id': fpdUserIdVal + }, + 'imp': [{ + 'id': bidRequests[0].bidId, + 'tagid': bidRequests[0].params.uid, + 'ext': {'divid': bidRequests[0].adUnitCode}, + 'bidfloor': bidRequests[0].params.bidFloor, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[1].bidId, + 'tagid': bidRequests[1].params.uid, + 'ext': {'divid': bidRequests[1].adUnitCode}, + 'banner': { + 'w': 300, + 'h': 250, + 'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}] + } + }, { + 'id': bidRequests[2].bidId, + 'tagid': bidRequests[2].params.uid, + 'ext': {'divid': bidRequests[2].adUnitCode}, + 'video': { + 'w': 400, + 'h': 600, + 'protocols': [1, 2, 3], + 'mimes': ['video/mp4', 'video/webm', 'application/javascript', 'video/ogg'], + } + }, { + 'id': bidRequests[3].bidId, + 'tagid': bidRequests[3].params.uid, + 'ext': {'divid': bidRequests[3].adUnitCode}, + 'banner': { + 'w': 728, + 'h': 90, + 'format': [{'w': 728, 'h': 90}] + }, + 'video': { + 'w': 400, + 'h': 600, + 'protocols': [1, 2, 3] + } + }] + }); + + expect(criteoPayload).to.deep.equal({ + 'publisher': { + 'ext': undefined, + 'url': bidderRequest.refererInfo.page, + 'networkid': 125 + }, + 'regs': { + 'coppa': undefined + }, + 'slots': [{ + 'impid': bidRequests[0].adUnitCode, + 'transactionid': bidderRequest.transactionId, + 'auctionId': bidderRequest.auctionId, + 'sizes': ['300x250', '300x600'] + }, { + 'impid': bidRequests[2].adUnitCode, + 'transactionid': bidderRequest.transactionId, + 'auctionId': bidderRequest.auctionId, + 'sizes': [], + 'video': { + 'api': [ + 1, + 2 + ], + 'maxduration': 30, + 'mimes': [ + 'video/mp4', + 'video/webm', + 'application/javascript', + 'video/ogg', + ], + 'minduration': 0, + 'placement': 1, + 'playbackmethod': 1, + 'playersizes': [ + '400x600' + ], + 'protocols': [ + 1, + 2, + 3 + ], + 'skip': 1, + 'startdelay': 0 + } + }], + 'user': { + 'ext': undefined + } + }); + + getDataFromLocalStorageStub.restore(); + }); + it('if gdprConsent is present payload must have gdpr params', function () { const gdprBidderRequest = Object.assign({gdprConsent: {consentString: 'AAA', gdprApplies: true}}, bidderRequest); const [request] = spec.buildRequests(bidRequests, gdprBidderRequest); @@ -683,24 +840,6 @@ describe('TheMediaGrid Adapter', function () { expect(payload.site.content.id).to.equal(contentId); }); - it('should be right tmax when timeout in config is less then timeout in bidderRequest', function() { - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'bidderTimeout' ? 2000 : null); - const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload.tmax).to.equal(2000); - getConfigStub.restore(); - }); - it('should be right tmax when timeout in bidderRequest is less then timeout in config', function() { - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'bidderTimeout' ? 5000 : null); - const [request] = spec.buildRequests([bidRequests[0]], bidderRequest); - expect(request.data).to.be.an('string'); - const payload = parseRequest(request.data); - expect(payload.tmax).to.equal(3000); - getConfigStub.restore(); - }); it('should contain regs.coppa if coppa is true in config', function () { const getConfigStub = sinon.stub(config, 'getConfig').callsFake( arg => arg === 'coppa' ? true : null); @@ -1365,6 +1504,137 @@ describe('TheMediaGrid Adapter', function () { expect(result).to.deep.equal(expectedResponse); }); + it('should add response with biggest price', function () { + const mainResponse = [ + {'bid': [{'impid': '2164be6358b9', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, + {'bid': [{'impid': '4e111f1b66e4', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, + ]; + const criteoResponse = [ + { + impid: 'adunit-code-1', + cpm: 0.15, + creative: '
test content 3
', + creativecode: 1, + bidId: '2164be6358b9', + currency: 'USD', + width: 300, + height: 250, + dealCode: 11, + zoneid: 123, + ttl: 360, + adomain: ['criteo.com'], + }, + { + impid: 'adunit-code-1', + cpm: 1, + creative: '
test content 4
', + creativecode: 2, + bidId: '4e111f1b66e4', + currency: 'USD', + width: 300, + height: 600, + dealCode: 12, + zoneid: 456, + ttl: 360, + adomain: ['criteo.com'], + } + ]; + const bidRequests = [ + { + 'bidder': 'grid', + 'params': { + 'uid': '1', + 'zoneId': 123, + 'withCriteo': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '2164be6358b9', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + 'transactionId': '43534f55b213ac', + }, + { + 'bidder': 'grid', + 'params': { + 'uid': '2', + 'zoneId': 456, + 'withCriteo': true + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4e111f1b66e4', + 'bidderRequestId': '106efe3247', + 'auctionId': '32a1f276cb87cb8', + 'transactionId': '43534f55b213ac', + } + ]; + const bidderRequest = { + refererInfo: { page: 'https://example.com' }, + bidderRequestId: '106efe3247', + transactionId: '43534f55b213ac', + auctionId: '32a1f276cb87cb8', + timeout: 3000 + }; + const [mainRequest, criteoRequest] = spec.buildRequests(bidRequests, bidderRequest); + const expectedMainResponse = [ + { + 'requestId': '2164be6358b9', + 'cpm': 1.15, + 'creativeId': 1, + 'dealId': 11, + 'width': 300, + 'height': 250, + 'ad': '
test content 1
', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, + }, + { + 'requestId': '4e111f1b66e4', + 'cpm': 0.5, + 'creativeId': 2, + 'dealId': 12, + 'width': 300, + 'height': 600, + 'ad': '
test content 2
', + 'currency': 'USD', + 'mediaType': 'banner', + 'netRevenue': true, + 'ttl': 360, + 'meta': { + advertiserDomains: [] + }, + } + ]; + const expectedCriteoResponse = [ + { + 'requestId': '4e111f1b66e4', + 'cpm': 1, + 'creativeId': 2, + 'dealId': 12, + 'width': 300, + 'height': 600, + 'ad': '
test content 4
', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360, + 'meta': { + advertiserDomains: ['criteo.com'] + }, + } + ]; + + const mainResult = spec.interpretResponse({'body': {'seatbid': mainResponse}}, mainRequest); + const criteoResult = spec.interpretResponse({'body': {'slots': criteoResponse}}, criteoRequest); + expect(mainResult).to.deep.equal(expectedMainResponse); + expect(criteoResult).to.deep.equal(expectedCriteoResponse); + }); + it('response with ext.bidder.grid.demandSource', function () { const bidRequests = [ { diff --git a/test/spec/modules/growthCodeAnalyticsAdapter_spec.js b/test/spec/modules/growthCodeAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..e542a2641e8 --- /dev/null +++ b/test/spec/modules/growthCodeAnalyticsAdapter_spec.js @@ -0,0 +1,70 @@ +import adapterManager from '../../../src/adapterManager.js'; +import growthCodeAnalyticsAdapter from '../../../modules/growthCodeAnalyticsAdapter.js'; +import { expect } from 'chai'; +import * as events from '../../../src/events.js'; +import constants from '../../../src/constants.json'; +import { generateUUID } from '../../../src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('growthCode analytics adapter', () => { + beforeEach(() => { + growthCodeAnalyticsAdapter.enableAnalytics({ + provider: 'growthCodeAnalytics', + options: { + pid: 'TEST01', + trackEvents: [ + 'auctionInit', + 'auctionEnd', + 'bidAdjustment', + 'bidTimeout', + 'bidTimeout', + 'bidRequested', + 'bidResponse', + 'setTargeting', + 'requestBids', + 'addAdUnits', + 'noBid', + 'bidWon', + 'bidderDone'] + } + }); + }); + + afterEach(() => { + growthCodeAnalyticsAdapter.disableAnalytics(); + }); + + it('registers itself with the adapter manager', () => { + const adapter = adapterManager.getAnalyticsAdapter('growthCodeAnalytics'); + expect(adapter).to.exist; + expect(adapter.adapter).to.equal(growthCodeAnalyticsAdapter); + }); + + it('tolerates undefined or empty config', () => { + growthCodeAnalyticsAdapter.enableAnalytics(undefined); + growthCodeAnalyticsAdapter.enableAnalytics({}); + }); + + it('sends auction end events to the backend', () => { + const auction = { + auctionId: generateUUID(), + adUnits: [{ + code: 'usr1234', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600], [728, 90]] + } + }, + adUnitCodes: ['usr1234'] + }], + }; + events.emit(constants.EVENTS.AUCTION_END, auction); + assert(server.requests.length > 0) + const body = JSON.parse(server.requests[0].requestBody); + var eventTypes = []; + body.events.forEach(e => eventTypes.push(e.eventType)); + assert(eventTypes.length > 0) + assert(eventTypes.indexOf(constants.EVENTS.AUCTION_END) > -1); + growthCodeAnalyticsAdapter.disableAnalytics(); + }); +}); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 17fff31f132..a2dacb16b73 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -1,5 +1,6 @@ import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec } from 'modules/gumgumBidAdapter.js'; @@ -484,6 +485,20 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; expect(bidRequest.data).to.not.include.any.keys('gdprConsent') }); + it('should not set coppa parameter if coppa config is set to false', function () { + config.setConfig({ + coppa: false + }); + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.coppa).to.eq(undefined); + }); + it('should set coppa parameter to 1 if coppa config is set to true', function () { + config.setConfig({ + coppa: true + }); + const bidRequest = spec.buildRequests(bidRequests)[0]; + expect(bidRequest.data.coppa).to.eq(1); + }); it('should add uspConsent parameter if it is present in the bidderRequest', function () { const noUspBidRequest = spec.buildRequests(bidRequests)[0]; const uspConsentObj = { uspConsent: '1YYY' }; diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index ca9eadc7fd4..c998ef2cf14 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -24,7 +24,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid?partner_id=0&_it=prebid`); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); it('gets a cached hadronid', function() { @@ -33,10 +33,8 @@ describe('HadronIdSystem', function () { }; getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1'); - const callbackSpy = sinon.spy(); - const callback = hadronIdSubmodule.getId(config).callback; - callback(callbackSpy); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'tstCachedHadronId1'}); + const result = hadronIdSubmodule.getId(config); + expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } }); }); it('allows configurable id url', function() { @@ -51,7 +49,7 @@ describe('HadronIdSystem', function () { const request = server.requests[0]; expect(request.url).to.eq('https://hadronid.publync.com?partner_id=0&_it=prebid'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); }); diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js new file mode 100644 index 00000000000..e55befd213a --- /dev/null +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -0,0 +1,194 @@ +import { expect } from 'chai' +import { spec } from 'modules/holidBidAdapter.js' + +describe('holidBidAdapterTests', () => { + const bidRequestData = { + bidder: 'holid', + adUnitCode: 'test-div', + bidId: 'bid-id', + auctionId: 'test-id', + params: { adUnitID: '12345' }, + mediaTypes: { banner: {} }, + sizes: [[300, 250]], + ortb2: { + site: { + publisher: { + domain: 'https://foo.bar', + } + }, + regs: { + gdpr: 1, + }, + user: { + ext: { + consent: 'G4ll0p1ng_Un1c0rn5', + } + }, + device: { + h: 410, + w: 1860, + } + } + } + + describe('isBidRequestValid', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + + it('should return true', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false when required params are not passed', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + delete bid.params.adUnitID + + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', () => { + const bid = JSON.parse(JSON.stringify(bidRequestData)) + const request = spec.buildRequests([bid], bid) + const payload = JSON.parse(request[0].data) + + it('should include ext in imp', () => { + expect(payload.imp[0].ext).to.exist + expect(payload.imp[0].ext).to.deep.equal({ + prebid: { storedrequest: { id: '12345' } }, + }) + }) + + it('should include banner format in imp', () => { + expect(payload.imp[0].banner).to.exist + expect(payload.imp[0].banner).to.deep.equal({ + format: [{ w: 300, h: 250 }], + }) + }) + + it('should include ortb2 first party data', () => { + expect(payload.device.w).to.equal(1860) + expect(payload.device.h).to.equal(410) + expect(payload.user.ext.consent).to.equal('G4ll0p1ng_Un1c0rn5') + expect(payload.regs.gdpr).to.equal(1) + }) + }) + + describe('interpretResponse', () => { + const serverResponse = { + body: { + id: 'test-id', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: 'testbidid', + price: 0.4, + adm: 'test-ad', + adid: 789456, + crid: 1234, + w: 300, + h: 250, + }, + ], + }, + ], + }, + } + + const interpretedResponse = spec.interpretResponse( + serverResponse, + bidRequestData + ) + + it('should interpret response', () => { + expect(interpretedResponse[0].requestId).to.equal(bidRequestData.bidId) + expect(interpretedResponse[0].cpm).to.equal( + serverResponse.body.seatbid[0].bid[0].price + ) + expect(interpretedResponse[0].ad).to.equal( + serverResponse.body.seatbid[0].bid[0].adm + ) + expect(interpretedResponse[0].creativeId).to.equal( + serverResponse.body.seatbid[0].bid[0].crid + ) + expect(interpretedResponse[0].width).to.equal( + serverResponse.body.seatbid[0].bid[0].w + ) + expect(interpretedResponse[0].height).to.equal( + serverResponse.body.seatbid[0].bid[0].h + ) + expect(interpretedResponse[0].currency).to.equal(serverResponse.body.cur) + }) + }) + + describe('getUserSyncs', () => { + it('should return user sync', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: { + responsetimemillis: { + 'test seat 1': 2, + 'test seat 2': 1, + }, + }, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [ + { + type: 'iframe', + url: 'https://null.holid.io/sync.html?bidders=%5B%22test%20seat%201%22%2C%22test%20seat%202%22%5D&gdpr=1&gdpr_consent=dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig&usp_consent=mkjvbiniwot4827obfoy8sdg8203gb&type=iframe', + }, + ] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) + + it('should return empty user syncs when responsetimemillis is not defined', () => { + const optionsType = { + iframeEnabled: true, + pixelEnabled: true, + } + const serverResponse = [ + { + body: { + ext: {}, + }, + }, + ] + const gdprConsent = { + gdprApplies: 1, + consentString: 'dkj49Sjmfjuj34as:12jaf90123hufabidfy9u23brfpoig', + } + const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb' + const expectedUserSyncs = [] + + const userSyncs = spec.getUserSyncs( + optionsType, + serverResponse, + gdprConsent, + uspConsent + ) + + expect(userSyncs).to.deep.equal(expectedUserSyncs) + }) + }) +}) diff --git a/test/spec/modules/iasRtdProvider_spec.js b/test/spec/modules/iasRtdProvider_spec.js index feb4c1093a8..ec4d2bd437a 100644 --- a/test/spec/modules/iasRtdProvider_spec.js +++ b/test/spec/modules/iasRtdProvider_spec.js @@ -54,6 +54,59 @@ describe('iasRtdProvider is a RTD provider that', function () { const value = iasSubModule.init(config); expect(value).to.equal(true); }); + it('returns true with the pubId, keyMappings and adUnitPath params', function () { + const config = { + name: 'ias', + waitForIt: true, + params: { + pubId: '123456', + keyMappings: { + 'id': 'ias_id' + }, + adUnitPath: {'one-div-id': '/012345/ad/unit/path'} + } + }; + const value = iasSubModule.init(config); + expect(value).to.equal(true); + }); + it('returns true with the pubId and adUnitPath params with multiple keys', function () { + const config = { + name: 'ias', + waitForIt: true, + params: { + pubId: '123456', + keyMappings: { + 'id': 'ias_id' + }, + adUnitPath: { + 'one-div-id': '/012345/ad/unit/path', + 'another-div-id': '/012345/ad/unit/path', + 'third-div-id': '/012345/another/ad/unit/path' + } + } + }; + const value = iasSubModule.init(config); + expect(value).to.equal(true); + }); + it('returns true with the pubId and adUnitPath params with empty values', function () { + const config = { + name: 'ias', + waitForIt: true, + params: { + pubId: '123456', + keyMappings: { + 'id': 'ias_id' + }, + adUnitPath: { + 'one-div-id': '/012345/ad/unit/path', + 'another-div-id': '', + 'third-div-id': '' + } + } + }; + const value = iasSubModule.init(config); + expect(value).to.equal(true); + }); }); describe('has a method `getBidRequestData` that', function () { it('exists', function () { @@ -138,7 +191,10 @@ const config = { keyMappings: { 'id': 'ias_id' }, - pageUrl: 'https://integralads.com/test' + pageUrl: 'https://integralads.com/test', + adUnitPath: { + 'one-div-id': '/012345/ad/unit/path' + } } }; diff --git a/test/spec/modules/impactifyBidAdapter_spec.js b/test/spec/modules/impactifyBidAdapter_spec.js index 8bb2d089ad8..215972ff450 100644 --- a/test/spec/modules/impactifyBidAdapter_spec.js +++ b/test/spec/modules/impactifyBidAdapter_spec.js @@ -166,6 +166,19 @@ describe('ImpactifyAdapter', function () { } }; + it('should pass bidfloor', function () { + videoBidRequests[0].getFloor = function() { + return { + currency: 'USD', + floor: 1.23, + } + } + + const res = spec.buildRequests(videoBidRequests, videoBidderRequest) + const resData = JSON.parse(res.data) + expect(resData.imp[0].bidfloor).to.equal(1.23) + }); + it('sends video bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(videoBidRequests, videoBidderRequest); expect(request.url).to.equal(ORIGIN + AUCTIONURI); diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index d88266af386..400b9145e0b 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1,14 +1,30 @@ import { expect } from 'chai'; -import { spec } from 'modules/improvedigitalBidAdapter.js'; +import {CONVERTER, spec} from 'modules/improvedigitalBidAdapter.js'; import { config } from 'src/config.js'; import { deepClone } from 'src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; import { deepSetValue } from '../../../src/utils'; +// load modules that register ORTB processors +import 'src/prebid.js'; +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {decorateAdUnitsWithNativeParams, toLegacyResponse} from '../../../src/native.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {hook} from '../../../src/hook.js'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; - const AD_SERVER_URL = 'https://ad.360yield.com/pb'; - const BASIC_ADS_URL = 'https://ad.360yield-basic.com/pb'; + const AD_SERVER_BASE_URL = 'https://ad.360yield.com'; + const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com'; + const PB_ENDPOINT = 'pb'; + const AD_SERVER_URL = `${AD_SERVER_BASE_URL}/${PB_ENDPOINT}`; + const BASIC_ADS_URL = `${BASIC_ADS_BASE_URL}/${PB_ENDPOINT}`; const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; const INSTREAM_TYPE = 1; @@ -93,6 +109,7 @@ describe('Improve Digital Adapter Tests', function () { }; const simpleSmartTagBidRequest = { + mediaTypes: {}, bidder: 'improvedigital', bidId: '1a2b3c', placementCode: 'placement1', @@ -103,27 +120,27 @@ describe('Improve Digital Adapter Tests', function () { }; const bidderRequest = { - bids: [simpleBidRequest] + bids: [simpleBidRequest], }; const extendBidderRequest = { - bids: [extendBidRequest] + bids: [extendBidRequest], }; const instreamBidderRequest = { - bids: [instreamBidRequest] + bids: [instreamBidRequest], }; const outstreamBidderRequest = { - bids: [outstreamBidRequest] + bids: [outstreamBidRequest], }; const multiFormatBidderRequest = { - bids: [multiFormatBidRequest] + bids: [multiFormatBidRequest], }; const nativeBidderRequest = { - bids: [nativeBidRequest] + bids: [nativeBidRequest], }; const gdprConsent = { @@ -147,6 +164,16 @@ describe('Improve Digital Adapter Tests', function () { }, }; + function updateNativeParams(bidRequests) { + bidRequests = deepClone(bidRequests); + decorateAdUnitsWithNativeParams(bidRequests); + return bidRequests; + } + + before(() => { + hook.ready(); + }); + describe('isBidRequestValid', function () { it('should return false when no bid', function () { expect(spec.isBidRequestValid()).to.equal(false); @@ -199,24 +226,22 @@ describe('Improve Digital Adapter Tests', function () { it('should make a well-formed request objects', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + const request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); expect(request.url).to.equal(AD_SERVER_URL); - expect(request.bidderRequest).to.deep.equal(bidderRequest); const payload = JSON.parse(request.data); expect(payload).to.be.an('object'); expect(payload.id).to.be.a('string'); expect(payload.tmax).not.to.exist; - expect(payload.cur).to.be.an('array'); expect(payload.regs).to.not.exist; expect(payload.schain).to.not.exist; - expect(payload.source).to.deep.equal({ ext: {}, tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb' }); + sinon.assert.match(payload.source, {tid: 'f183e871-fbed-45f0-a427-c8a63c4c01eb'}) expect(payload.device).to.be.an('object'); expect(payload.user).to.not.exist; - expect(payload.imp).to.deep.equal([ - { + sinon.assert.match(payload.imp, [ + sinon.match({ id: '33e9500b21129f', secure: 0, ext: { @@ -230,28 +255,23 @@ describe('Improve Digital Adapter Tests', function () { {w: 160, h: 600}, ] } - } + }) ]); }); it('should make a well-formed request object for multi-format ad unit', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); - const request = spec.buildRequests([multiFormatBidRequest], multiFormatBidderRequest)[0]; + const request = spec.buildRequests(updateNativeParams([multiFormatBidRequest]), multiFormatBidderRequest)[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); expect(request.url).to.equal(AD_SERVER_URL); - expect(request.bidderRequest).to.deep.equal(multiFormatBidderRequest); const payload = JSON.parse(request.data); expect(payload).to.be.an('object'); - expect(payload.imp).to.deep.equal([ - { + sinon.assert.match(payload.imp, [ + sinon.match({ id: '33e9500b21129f', - native: { - request: '{"eventtrackers":[{"event":1,"methods":[1,2]}],"assets":[{"id":3,"required":1,"data":{"type":2}}]}', - ver: '1.2' - }, secure: 0, ext: { bidder: { @@ -270,53 +290,54 @@ describe('Improve Digital Adapter Tests', function () { {w: 160, h: 600}, ] } - } + }) ]); + if (FEATURES.NATIVE) { + sinon.assert.match(payload.imp[0], { + native: { + ver: '1.2' + }, + }) + const nativeReq = JSON.parse(payload.imp[0].native.request); + sinon.assert.match(nativeReq, { + eventtrackers: [ + {event: 1, methods: [1, 2]}, + ], + 'assets': [ + sinon.match({'required': 1, 'data': {'type': 2}}) + ] + }); + } }); - it('should make a well-formed native request', function () { - const payload = JSON.parse(spec.buildRequests([nativeBidRequest], {})[0].data); - expect(payload.imp[0].native).to.deep.equal({ - ver: '1.2', - request: '{"eventtrackers":[{"event":1,"methods":[1,2]}],"assets":[{"id":0,"required":1,"title":{"len":140}},{"id":3,"required":1,"data":{"type":2}}]}' + if (FEATURES.NATIVE) { + it('should make a well-formed native request', function () { + const payload = JSON.parse(spec.buildRequests(updateNativeParams([nativeBidRequest]), {})[0].data); + const nativeReq = JSON.parse(payload.imp[0].native.request); + sinon.assert.match(nativeReq, { + eventtrackers: [ + {event: 1, methods: [1, 2]}, + ], + assets: [ + sinon.match({required: 1, title: {len: 140}}), + sinon.match({required: 1, data: {type: 2}}) + ] + }) }); - }); - it('should not make native request when nativeParams is undefined', function () { - const request = deepClone(nativeBidRequest); - delete request.nativeParams; - const payload = JSON.parse(spec.buildRequests([request], {})[0].data); - expect(payload.imp[0].native).to.not.exist; - }); - - it('should not make native request when no assets', function () { - const request = deepClone(nativeBidRequest); - request.nativeParams = {}; - const payload = JSON.parse(spec.buildRequests([request], {})[0].data); - expect(payload.imp[0].native).to.not.exist; - }); - - it('should make a well-formed native request', function () { - const payload = JSON.parse(spec.buildRequests([nativeBidRequest], {})[0].data); - expect(payload.imp[0].native).to.deep.equal({ - ver: '1.2', - request: '{"eventtrackers":[{"event":1,"methods":[1,2]}],"assets":[{"id":0,"required":1,"title":{"len":140}},{"id":3,"required":1,"data":{"type":2}}]}' + it('should not make native request when nativeOrtbRequest is undefined', function () { + const requests = updateNativeParams([nativeBidRequest]); + delete requests[0].nativeOrtbRequest; + const payload = JSON.parse(spec.buildRequests(requests, {})[0].data); + expect(payload.imp[0].native).to.not.exist; }); - }); - - it('should not make native request when nativeParams is undefined', function () { - const request = deepClone(nativeBidRequest); - delete request.nativeParams; - const payload = JSON.parse(spec.buildRequests([request], {})[0].data); - expect(payload.imp[0].native).to.not.exist; - }); - it('should not make native request when no assets', function () { - const request = deepClone(nativeBidRequest); - request.nativeParams = {}; - const payload = JSON.parse(spec.buildRequests([request], {})[0].data); - expect(payload.imp[0].native).to.not.exist; - }); + it('should not make native request when no assets', function () { + const requests = updateNativeParams([{...nativeBidRequest, nativeParams: {}}]) + const payload = JSON.parse(spec.buildRequests(requests, {})[0].data); + expect(payload.imp[0].native).to.not.exist; + }); + } it('should set placementKey and publisherId for smart tags', function () { const payload = JSON.parse(spec.buildRequests([simpleSmartTagBidRequest], bidderRequest)[0].data); @@ -337,10 +358,14 @@ describe('Improve Digital Adapter Tests', function () { }); it('should add currency', function () { - const bidRequest = Object.assign({}, simpleBidRequest); - getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); - const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.cur).to.deep.equal(['JPY']); + config.setConfig({currency: {adServerCurrency: 'JPY'}}); + try { + const bidRequest = Object.assign({}, simpleBidRequest); + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.cur).to.deep.equal(['JPY']); + } finally { + config.resetConfig(); + } }); it('should add bid floor', function () { @@ -371,9 +396,10 @@ describe('Improve Digital Adapter Tests', function () { it('should add GDPR consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); + const payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); expect(payload.user.ext.consent).to.equal('CONSENT'); + expect(payload.user.ext.ConsentedProvidersSettings).to.not.exist; expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); }); @@ -381,13 +407,22 @@ describe('Improve Digital Adapter Tests', function () { const bidderRequestGdprEmptyAddtl = deepClone(bidderRequestGdpr); bidderRequestGdprEmptyAddtl.gdprConsent.addtlConsent = '1~'; const bidRequest = Object.assign({}, simpleBidRequest); - const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdprEmptyAddtl)[0].data); + const payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdprEmptyAddtl))[0].data); + expect(payload.user.ext.consented_providers_settings).to.not.exist; + }); + + it('should add ConsentedProvidersSettings when extend mode enabled', function () { + const bidRequest = deepClone(extendBidRequest); + const payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); + expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); + expect(payload.user.ext.consent).to.equal('CONSENT'); + expect(payload.user.ext.ConsentedProvidersSettings.consented_providers).to.exist.and.to.equal('1~1.35.41.101'); expect(payload.user.ext.consented_providers_settings).to.not.exist; }); it('should add CCPA consent string', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], {...bidderRequest, ...{ uspConsent: '1YYY' }}); + const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); const payload = JSON.parse(request[0].data); expect(payload.regs.ext.us_privacy).to.equal('1YYY'); }); @@ -396,17 +431,17 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('coppa').returns(true); let bidRequest = Object.assign({}, simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); + let payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.coppa).to.equal(1); getConfigStub.withArgs('coppa').returns(false); bidRequest = Object.assign({}, simpleBidRequest); - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequestGdpr)[0].data); + payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.coppa).to.equal(0); }); it('should add referrer', function () { const bidRequest = Object.assign({}, simpleBidRequest); - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; const payload = JSON.parse(request.data); expect(payload.site.page).to.equal('https://blah.com/test.html'); }); @@ -486,40 +521,6 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].video.w).equal(640); }); - it('should set skip params only if skip=1', function() { - const bidRequest = deepClone(instreamBidRequest); - // 1 - const videoTest = { - skip: 1, - skipmin: 5, - skipafter: 30 - } - bidRequest.params.video = videoTest; - let request = spec.buildRequests([bidRequest], {})[0]; - let payload = JSON.parse(request.data); - expect(payload.imp[0].video.skip).to.equal(1); - expect(payload.imp[0].video.skipmin).to.equal(5); - expect(payload.imp[0].video.skipafter).to.equal(30); - - // 0 - leave out skipmin and skipafter - videoTest.skip = 0; - bidRequest.params.video = videoTest; - request = spec.buildRequests([bidRequest], {})[0]; - payload = JSON.parse(request.data); - expect(payload.imp[0].video.skip).to.equal(0); - expect(payload.imp[0].video.skipmin).to.not.exist; - expect(payload.imp[0].video.skipafter).to.not.exist; - - // other - videoTest.skip = 'blah'; - bidRequest.params.video = videoTest; - request = spec.buildRequests([bidRequest], {})[0]; - payload = JSON.parse(request.data); - expect(payload.imp[0].video.skip).to.not.exist; - expect(payload.imp[0].video.skipmin).to.not.exist; - expect(payload.imp[0].video.skipafter).to.not.exist; - }); - it('should ignore invalid/unexpected video params', function() { const bidRequest = deepClone(instreamBidRequest); // 1 @@ -583,7 +584,7 @@ describe('Improve Digital Adapter Tests', function () { }] }]}}; const bidRequest = Object.assign({}, simpleBidRequest); - bidRequest.userId = userId; + bidRequest.userIdAsEids = createEidsArray(userId); const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; const payload = JSON.parse(request.data); expect(payload.user).to.deep.equal(expectedUserObject); @@ -596,8 +597,6 @@ describe('Improve Digital Adapter Tests', function () { ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(2); - expect(requests[0].bidderRequest).to.deep.equal(bidderRequest); - expect(requests[1].bidderRequest).to.deep.equal(bidderRequest); }); it('should return one request in a single request mode', function () { @@ -632,7 +631,7 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); - expect(payload.imp[0].banner).to.deep.equal({ + sinon.assert.match(payload.imp[0].banner, { format: [ { w: 300, h: 250 }, { w: 160, h: 600 } @@ -651,7 +650,7 @@ describe('Improve Digital Adapter Tests', function () { bidRequest.params.size = size; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); - expect(payload.imp[0].banner).to.deep.equal({ + sinon.assert.match(payload.imp[0].banner, { format: [ { w: 300, h: 250 }, { w: 160, h: 600 } @@ -659,44 +658,6 @@ describe('Improve Digital Adapter Tests', function () { }); }); - it('should set GPID and Instl Signal', function () { - const bidRequest = Object.assign({ - ortb2Imp: { - instl: true, - ext: { - gpid: '/123/ID-FORMAT', - data: { - pbadslot: '/123/ID-FORMAT-PBADSLOT', - adserver: { - adslot: '/123/ID-FORMAT-ADSERVER-PB-ADSLOT', - } - } - }, - } - }, simpleBidRequest); - let request = spec.buildRequests([bidRequest], bidderRequest)[0]; - let payload = JSON.parse(request.data); - expect(payload.imp[0].ext.gpid).to.equal('/123/ID-FORMAT'); - expect(payload.imp[0].instl).to.equal(1); - - delete bidRequest.ortb2Imp.ext.gpid; - request = spec.buildRequests([bidRequest], bidderRequest)[0]; - payload = JSON.parse(request.data); - expect(payload.imp[0].ext.gpid).to.equal('/123/ID-FORMAT-PBADSLOT'); - - delete bidRequest.ortb2Imp.ext.data.pbadslot; - request = spec.buildRequests([bidRequest], bidderRequest)[0]; - payload = JSON.parse(request.data); - expect(payload.imp[0].ext.gpid).to.equal('/123/ID-FORMAT-ADSERVER-PB-ADSLOT'); - - delete bidRequest.ortb2Imp.ext.data.adserver; - delete bidRequest.ortb2Imp.instl; - request = spec.buildRequests([bidRequest], bidderRequest)[0]; - payload = JSON.parse(request.data); - expect(payload.imp[0].ext.gpid).to.not.exist; - expect(payload.imp[0].instl).to.not.exist; - }); - it('should not set site when app is defined in FPD', function () { const ortb2 = {app: {content: 'XYZ'}}; let request = spec.buildRequests([simpleBidRequest], {...bidderRequest, ortb2})[0]; @@ -723,21 +684,21 @@ describe('Improve Digital Adapter Tests', function () { page: 'https://improveditigal.com/', domain: 'improveditigal.com' }); - let request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; let payload = JSON.parse(request.data); expect(payload.site.content).does.exist.and.equal('XYZ'); expect(payload.site.page).does.exist.and.equal('https://improveditigal.com/'); expect(payload.site.domain).does.exist.and.equal('improveditigal.com'); getConfigStub.reset(); - request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; payload = JSON.parse(request.data); expect(payload.site.content).does.not.exist; expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); const ortb2 = {site: {content: 'ZZZ'}}; - request = spec.buildRequests([simpleBidRequest], {...bidderRequestReferrer, ortb2})[0]; + request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest({...bidderRequestReferrer, ortb2}))[0]; payload = JSON.parse(request.data); expect(payload.site.content).does.exist.and.equal('ZZZ'); expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); @@ -747,6 +708,7 @@ describe('Improve Digital Adapter Tests', function () { it('should set site when app not available', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns(undefined); + getConfigStub.withArgs('site').returns({}); let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; let payload = JSON.parse(request.data); expect(payload.site).does.exist; @@ -810,6 +772,64 @@ describe('Improve Digital Adapter Tests', function () { expect(requests[0].url).to.equal(AD_SERVER_URL); expect(requests[1].url).to.equal(EXTEND_URL); }); + + it('should add publisherId to request URL when available in request params', function() { + function formatPublisherUrl(baseUrl, publisherId) { + return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`; + } + const bidRequest = deepClone(simpleBidRequest); + bidRequest.params.publisherId = 1000; + let request = spec.buildRequests([bidRequest], bidderRequest)[0]; + expect(request).to.be.an('object'); + sinon.assert.match(request, { + method: METHOD, + url: formatPublisherUrl(AD_SERVER_BASE_URL, 1000), + bidderRequest + }); + + const bidRequest2 = deepClone(simpleBidRequest) + bidRequest2.params.publisherId = 1002; + + const bidRequest3 = deepClone(extendBidRequest) + bidRequest3.params.publisherId = 1002; + + const request1 = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0]; + expect(request1.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000)); + const request2 = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[1]; + expect(request2.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1002)); + const request3 = spec.buildRequests([bidRequest, bidRequest3], bidderRequest)[1]; + expect(request3.url).to.equal(EXTEND_URL); + + // Enable single request mode + getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.singleRequest').returns(true); + try { + spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0]; + } catch (e) { + expect(e.name).to.exist.equal('Error') + expect(e.message).to.exist.equal(`All Improve Digital placements in a single call must have the same publisherId. Please check your 'params.publisherId' or turn off the single request mode.`) + } + + bidRequest2.params.publisherId = null; + request = spec.buildRequests([bidRequest, bidRequest2], bidderRequest)[0]; + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000)); + + const consent = deepClone(gdprConsent); + deepSetValue(consent, 'vendorData.purpose.consents.1', false); + const bidderRequestWithConsent = deepClone(bidderRequest); + bidderRequestWithConsent.gdprConsent = consent; + request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0]; + expect(request.url).to.equal(formatPublisherUrl(BASIC_ADS_BASE_URL, 1000)); + + deepSetValue(consent, 'vendorData.purpose.consents.1', true); + bidderRequestWithConsent.gdprConsent = consent; + request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0]; + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000)); + + delete bidRequest.params.publisherId; + request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0]; + expect(request.url).to.equal(AD_SERVER_URL); + }); }); const serverResponse = { @@ -829,7 +849,6 @@ describe('Improve Digital Adapter Tests', function () { 'agency_id': '0' } }, - 'exp': 120, 'crid': '510265', 'price': 1.9200543539802946, 'id': '35adfe19-d6e9-46b9-9f7d-20da7026b965', @@ -872,7 +891,6 @@ describe('Improve Digital Adapter Tests', function () { 'agency_id': '0' } }, - 'exp': 120, 'crid': '510265', 'price': 1.9200543539802946, 'id': '35adfe19-d6e9-46b9-9f7d-20da7026b965', @@ -892,7 +910,6 @@ describe('Improve Digital Adapter Tests', function () { 'agency_id': '0' } }, - 'exp': 120, 'crid': '479163', 'price': 1.9200543539802946, 'id': '83c8d524-0955-4d0c-b558-4c9f3600e09b', @@ -937,10 +954,9 @@ describe('Improve Digital Adapter Tests', function () { } }, 'crid': '544456', - 'exp': 120, 'id': '52098fad-20c1-476b-a4fa-41e275e5a4a5', 'price': 1.8600000000000003, - 'adm': '{"ver":"1.1","imptrackers":["https://secure.adnxs.com/imptr?id=52311&t=2","https://euw-ice.360yield.com/imp_pixel?ic=hcUBlCANx1FabHBf6FR2gC7UO4xEyXahdZAn0-B5qL-bb3A74BJ1smyWIyW7IWcC0SOjSXzVpevTHXxTqJ.sf.Qhahyy6tSo.0j1QWfXlH8sM4-8vKWjMjw-x.IrJJNlwkQ0s1CdwcwTefcLXm5l2E-W19VhACuV7f3mgrZMNjiSw.SjJAfyPC3SIyAMRjYfj53UmjriQ46T7lhmkqxK8wHmksYCdbZc3PZESk8NWl28sxdjNvnYYCFMcJbeav.LOLabyTXfwy-1cEPbQs.IKMRZIKaqccTDPV3wOtzbNv0jQzatd3Nnv-PGFQcjQ-GW3i27W04Fws4kodpFSn-B6VwZAjzLzoyd5gBncyRnAyCplEbgHU5sZ1IyKHWjgCl3ZtRIK5vqrRD5D-xqgSnOi7-phG.CqZWDZ4bMDSfQg2ZnbvUTyGKcEl0WR59dW5izTMV4Fjizcrvr5T-t.zMbGwz.hGnmLIyhTqh.IcwW.GiDLVExlDlix5S1LXIWVsSyrQ=="],"assets":[{"id":1,"data":{"value":"ImproveDigital","type":1}},{"id":3,"data":{"value":"Test content.","type":2}},{"id":0,"title":{"text":"Sample Prebid Test Title"}}],"link":{"url":"https://euw-ice.360yield.com/click/hcUBlHOV7YhVse8RyBa0ajjyPa9Vt17e4g-1m3cRj3E67vq-RYux.SiUeAmBfNBcoOqkUc6A15AWmi4yFu5K-BdkaYjildyyk7fNLyR6hWr411kv4vrFwm5jrIBceuHS6K8oN69f.uCo8zGTdR2TbSlldwcpahQPlufZU.6VaMsu4IC53uEiUT5vb7kAw6TTlxuGBNq6zaGryiWEV2.N3YYJDTyYPh8tv-ZFyeFZFm0Gnjv.xWbC.70JcRUVU9UelQaPsTpTWYTXBhJt84YJUw1-GNtaLNVLSjjZbVoA2fsMti5p6OBmF.7u39on2OPgvseIkSmge7Pqg63pRqdP75hp.DAEk6OkcN1jGnwP2DSbvpaSbin5lVqjfO0B-wnQgfQTCUtM5v4JmkNweLhUf9Q-x.nPKLW5SccEk9ZFXzY2-1wpT3PWm8Tix3NRscLPZub9wHzL..pl6ip8cQ9hp16UjwT4H6RMAxL0R7bl-h2pAicGAzYmuO7ntRESKUoIWA==//http%3A%2F%2Fquantum-advertising.com%2Ffr%2F"},"jstracker":""}', + 'adm': "{\"ver\":\"1.1\",\"imptrackers\":[\"https://secure.adnxs.com/imptr?id=52311&t=2\",\"https://euw-ice.360yield.com/imp_pixel?ic=hcUBlCANx1FabHBf6FR2gC7UO4xEyXahdZAn0-B5qL-bb3A74BJ1smyWIyW7IWcC0SOjSXzVpevTHXxTqJ.sf.Qhahyy6tSo.0j1QWfXlH8sM4-8vKWjMjw-x.IrJJNlwkQ0s1CdwcwTefcLXm5l2E-W19VhACuV7f3mgrZMNjiSw.SjJAfyPC3SIyAMRjYfj53UmjriQ46T7lhmkqxK8wHmksYCdbZc3PZESk8NWl28sxdjNvnYYCFMcJbeav.LOLabyTXfwy-1cEPbQs.IKMRZIKaqccTDPV3wOtzbNv0jQzatd3Nnv-PGFQcjQ-GW3i27W04Fws4kodpFSn-B6VwZAjzLzoyd5gBncyRnAyCplEbgHU5sZ1IyKHWjgCl3ZtRIK5vqrRD5D-xqgSnOi7-phG.CqZWDZ4bMDSfQg2ZnbvUTyGKcEl0WR59dW5izTMV4Fjizcrvr5T-t.zMbGwz.hGnmLIyhTqh.IcwW.GiDLVExlDlix5S1LXIWVsSyrQ==\"],\"assets\":[{\"id\":1,\"data\":{\"value\":\"ImproveDigital\",\"type\":1}},{\"id\":3,\"data\":{\"value\":\"Test content.\",\"type\":2}},{\"id\":0,\"title\":{\"text\":\"Sample Prebid Test Title\"}}],\"link\":{\"url\":\"https://euw-ice.360yield.com/click/hcUBlHOV7YhVse8RyBa0ajjyPa9Vt17e4g-1m3cRj3E67vq-RYux.SiUeAmBfNBcoOqkUc6A15AWmi4yFu5K-BdkaYjildyyk7fNLyR6hWr411kv4vrFwm5jrIBceuHS6K8oN69f.uCo8zGTdR2TbSlldwcpahQPlufZU.6VaMsu4IC53uEiUT5vb7kAw6TTlxuGBNq6zaGryiWEV2.N3YYJDTyYPh8tv-ZFyeFZFm0Gnjv.xWbC.70JcRUVU9UelQaPsTpTWYTXBhJt84YJUw1-GNtaLNVLSjjZbVoA2fsMti5p6OBmF.7u39on2OPgvseIkSmge7Pqg63pRqdP75hp.DAEk6OkcN1jGnwP2DSbvpaSbin5lVqjfO0B-wnQgfQTCUtM5v4JmkNweLhUf9Q-x.nPKLW5SccEk9ZFXzY2-1wpT3PWm8Tix3NRscLPZub9wHzL..pl6ip8cQ9hp16UjwT4H6RMAxL0R7bl-h2pAicGAzYmuO7ntRESKUoIWA==//http%3A%2F%2Fquantum-advertising.com%2Ffr%2F\"},\"jstracker\":\"\"}", 'impid': '33e9500b21129f', 'cid': '196108' } @@ -968,7 +984,6 @@ describe('Improve Digital Adapter Tests', function () { 'agency_id': '0' } }, - 'exp': 120, 'crid': '484367', 'price': 9.600271769901472, 'id': 'b131fd7b-5759-4b72-800e-60e69291e7d9', @@ -1012,7 +1027,6 @@ describe('Improve Digital Adapter Tests', function () { 'agency_id': '0' } }, - 'exp': 120, 'crid': '544063', 'price': 1.9199364935359489, 'id': '1fcf4dd8-a783-48ed-b59c-8fc8eeccb024', @@ -1041,20 +1055,17 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '510265', dealId: 320896, netRevenue: false, mediaType: BANNER, - meta: { - advertiserDomains: [] - } } ]; const multiFormatExpectedBid = [ Object.assign({}, expectedBid[0], { - ad: '  ' + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', }) ]; @@ -1067,14 +1078,11 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '479163', dealId: 320896, netRevenue: false, mediaType: BANNER, - meta: { - advertiserDomains: [] - } } ]; @@ -1100,93 +1108,106 @@ describe('Improve Digital Adapter Tests', function () { content: expectedBidOutstreamVideo[0].vastXml }; + function makeRequest(bidderRequest) { + return { + ortbRequest: CONVERTER.toORTB({bidderRequest}) + } + } + + function expectMatch(actual, expected) { + sinon.assert.match(actual, expected.map(i => sinon.match(i))); + } + it('should return a well-formed display bid', function () { - const bids = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(bids).to.deep.equal(expectedBid); + const bids = spec.interpretResponse(serverResponse, makeRequest(bidderRequest)); + expectMatch(bids, expectedBid); }); it('should return a well-formed display bid for multi-format ad unit', function () { - const bids = spec.interpretResponse(serverResponse, {bidderRequest: multiFormatBidderRequest}); - expect(bids).to.deep.equal(multiFormatExpectedBid); + const bids = spec.interpretResponse(serverResponse, makeRequest(multiFormatBidderRequest)); + + expectMatch(bids, multiFormatExpectedBid); }); it('should return two bids', function () { - const bids = spec.interpretResponse(serverResponseTwoBids, {bidderRequest}); - expect(bids).to.deep.equal(expectedTwoBids); + const bids = spec.interpretResponse(serverResponseTwoBids, makeRequest(bidderRequest)); + expectMatch(bids, expectedTwoBids); }); it('should set dealId correctly', function () { + const request = makeRequest(bidderRequest); const response = deepClone(serverResponse); let bids; delete response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id; response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'deal_id'; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids[0].dealId).to.not.exist; response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; delete response.body.seatbid[0].bid[0].ext.improvedigital.buying_type; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids[0].dealId).to.not.exist; response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'rtb'; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids[0].dealId).to.not.exist; response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'classic'; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids[0].dealId).to.equal(268515); response.body.seatbid[0].bid[0].ext.improvedigital.line_item_id = 268515; response.body.seatbid[0].bid[0].ext.improvedigital.buying_type = 'deal_id'; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids[0].dealId).to.equal(268515); }); it('should set currency', function () { const response = deepClone(serverResponse); - response.body.cur = 'eur'; - const bids = spec.interpretResponse(response, {bidderRequest}); + response.body.cur = 'EUR'; + const bids = spec.interpretResponse(response, makeRequest(bidderRequest)); expect(bids[0].currency).to.equal('EUR'); }); it('should return empty array for bad response or no price', function () { + const request = makeRequest(bidderRequest); let response = deepClone(serverResponse); let bids; // Price missing or 0 response.body.seatbid[0].bid[0].price = 0; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids).to.deep.equal([]); delete response.body.seatbid[0].bid[0]; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids).to.deep.equal([]); response.body.seatbid[0].bid[0] = []; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids).to.deep.equal([]); // errorCode present response = deepClone(serverResponse); response.body.seatbid[0].bid[0].errorCode = undefined; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids).to.deep.equal([]); // adm and native missing response = deepClone(serverResponse); delete response.body.seatbid[0].bid[0].adm; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids).to.deep.equal([]); response.body.seatbid[0].bid[0].adm = null; - bids = spec.interpretResponse(response, {bidderRequest}); + bids = spec.interpretResponse(response, request); expect(bids).to.deep.equal([]); }); it('should set netRevenue', function () { const response = deepClone(serverResponse); response.body.seatbid[0].bid[0].ext.improvedigital.is_net = true; - const bids = spec.interpretResponse(response, {bidderRequest}); + const bids = spec.interpretResponse(response, makeRequest(bidderRequest)); expect(bids[0].netRevenue).to.equal(true); }); @@ -1194,59 +1215,53 @@ describe('Improve Digital Adapter Tests', function () { const adomain = ['domain.com']; const response = deepClone(serverResponse); response.body.seatbid[0].bid[0].adomain = adomain; - const bids = spec.interpretResponse(response, {bidderRequest}); + const bids = spec.interpretResponse(response, makeRequest(bidderRequest)); expect(bids[0].meta.advertiserDomains).to.equal(adomain); }); // // Native ads - it('should return a well-formed native ad bid', function () { - const bids = spec.interpretResponse(serverResponseNative, {bidderRequest: nativeBidderRequest}); - // Verify Native Response - expect(bids[0].native).to.exist; - const nativeBid = bids[0].native; - const nativeResp = JSON.parse(serverResponseNative.body.seatbid[0].bid[0].adm); - // Verify Native Response - expect(nativeBid.clickUrl).to.exist.and.equal(nativeResp.link.url); - expect(nativeBid.impressionTrackers).to.exist.and.deep.equal(nativeResp.imptrackers); - expect(nativeBid.javascriptTrackers).to.exist.and.deep.equal(nativeResp.jstracker); - - // Verify Assets - expect(nativeBid.title).to.exist.and.equal('Sample Prebid Test Title'); - expect(nativeBid.sponsoredBy).to.exist.and.equal('ImproveDigital'); - expect(nativeBid.body).to.exist.and.equal('Test content.'); - }); + if (FEATURES.NATIVE) { + it('should return a well-formed native ad bid', function () { + const reqBids = updateNativeParams(nativeBidderRequest.bids); + const request = makeRequest({ + ...nativeBidderRequest, + reqBids + }) + const bids = spec.interpretResponse(serverResponseNative, request); + expect(bids[0].native.ortb).to.eql(JSON.parse(serverResponseNative.body.seatbid[0].bid[0].adm)) + }); - it('should return a well-formed native bid for multi-format ad unit', function () { - const bids = spec.interpretResponse(serverResponseNative, {bidderRequest: multiFormatBidderRequest}); - expect(bids[0].mediaType).to.equal(NATIVE); - }); + it('should return a well-formed native bid for multi-format ad unit', function () { + const bids = spec.interpretResponse(serverResponseNative, makeRequest(multiFormatBidderRequest)); + expect(bids[0].mediaType).to.equal(NATIVE); + }); + } // Video it('should return a well-formed instream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: instreamBidderRequest}); - expect(bids).to.deep.equal(expectedBidInstreamVideo); + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(instreamBidderRequest)); + expectMatch(bids, expectedBidInstreamVideo); }); it('should return a well-formed outstream video bid', function () { - const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: outstreamBidderRequest}); + const bids = spec.interpretResponse(serverResponseVideo, makeRequest(outstreamBidderRequest)); expect(bids[0].renderer).to.exist; - delete (bids[0].renderer); - expect(bids).to.deep.equal(expectedBidOutstreamVideo); + expectMatch(bids, expectedBidOutstreamVideo); }); it('should return a well-formed outstream video bid for multi-format ad unit', function () { + const request = makeRequest(multiFormatBidderRequest); const videoResponse = deepClone(serverResponseVideo); - let bids = spec.interpretResponse(videoResponse, {bidderRequest: multiFormatBidderRequest}); + let bids = spec.interpretResponse(videoResponse, request); expect(bids[0].renderer).to.exist; - delete (bids[0].renderer); - expect(bids).to.deep.equal(expectedBidOutstreamVideo); + expectMatch(bids, expectedBidOutstreamVideo); videoResponse.body.seatbid[0].bid[0].adm = '

Ad from IncrementX

', slotBidId: 'bid-id-123456', + adType: '1', + settings: '1,2', nurl: 'htt://nurl.com', statusText: 'Success' } @@ -80,6 +82,8 @@ describe('IncrementX', function () { requestId: 'bid-id-123456', cpm: '0.7', currency: 'USD', + adType: '1', + settings: '1,2', netRevenue: false, width: '300', height: '250', diff --git a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js index d97b0925713..3b3542d43b0 100644 --- a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js +++ b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js @@ -1,5 +1,6 @@ import invisiblyAdapter from 'modules/invisiblyAnalyticsAdapter.js'; import { expect } from 'chai'; +import {expectEvents} from '../../helpers/analytics.js'; let events = require('src/events'); let constants = require('src/constants.json'); @@ -197,14 +198,8 @@ describe('Invisibly Analytics Adapter test suite', function () { account: 'invisibly', }, }); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); - // 5 Invisibly events + 1 Clean.io event - sinon.assert.callCount(invisiblyAdapter.track, 6); + expectEvents().to.beTrackedBy(invisiblyAdapter.track); }); it('should not catch events triggered without invisibly account config', function () { @@ -382,9 +377,6 @@ describe('Invisibly Analytics Adapter test suite', function () { expect(invisiblyEvents.event_data.pageViewId).to.exist; expect(invisiblyEvents.event_data.ver).to.equal(1); expect(invisiblyEvents.event_type).to.equal('PREBID_bidWon'); - - // 1 Invisibly event + 1 Clean.io event - sinon.assert.callCount(invisiblyAdapter.track, 2); }); // spec for bidder done event @@ -522,7 +514,6 @@ describe('Invisibly Analytics Adapter test suite', function () { expect(invisiblyEvents.event_data.auctionId).to.equal( MOCK.AUCTION_END.auctionId ); - sinon.assert.callCount(invisiblyAdapter.track, 1); }); // should not call sendEvent for events not supported by the adapter @@ -541,22 +532,21 @@ describe('Invisibly Analytics Adapter test suite', function () { it('track all event without errors', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); - events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); - events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); - events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); - events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); - events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); - events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); - - // 13 Invisibly events + 1 Clean.io event - sinon.assert.callCount(invisiblyAdapter.track, 14); + expectEvents([ + constants.EVENTS.AUCTION_INIT, + constants.EVENTS.AUCTION_END, + constants.EVENTS.BID_ADJUSTMENT, + constants.EVENTS.BID_TIMEOUT, + constants.EVENTS.BID_REQUESTED, + constants.EVENTS.BID_RESPONSE, + constants.EVENTS.NO_BID, + constants.EVENTS.BID_WON, + constants.EVENTS.BIDDER_DONE, + constants.EVENTS.SET_TARGETING, + constants.EVENTS.REQUEST_BIDS, + constants.EVENTS.ADD_AD_UNITS, + constants.EVENTS.AD_RENDER_FAILED + ]).to.beTrackedBy(invisiblyAdapter.track); }); }); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index b110c9a4872..5f104dce13f 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -286,6 +286,44 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_VIDEO_VALID_BID_MEDIUM_SIZE = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [2] + }, + size: [640, 480] + }, + sizes: [[640, 480], [200, 400]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480], [200, 400]] + } + }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47230' + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_MULTIFORMAT_BANNER_VALID_BID = [ { bidder: 'ix', @@ -364,6 +402,78 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_MULTIFORMAT_VALID_BID = [ + { + bidder: 'ix', + params: { + tagId: '123', + siteId: '456', + video: { + siteId: '1111' + }, + banner: { + siteId: '2222' + }, + native: { + siteId: '3333' + }, + size: [300, 250] + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[300, 250]], + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [1] + }, + banner: { + sizes: [[300, 250], [300, 600]] + }, + native: { + icon: { + required: false + }, + title: { + len: 25, + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47230', + data: { + pbadslot: 'div-gpt-ad-1460505748562-0' + } + } + }, + nativeOrtbRequest: { + assets: [{id: 0, required: 0, img: {type: 1}}, {id: 1, required: 1, title: {len: 140}}, {id: 2, required: 1, data: {type: 2}}, {id: 3, required: 1, img: {type: 3}}, {id: 4, required: false, video: {mimes: ['video/mp4', 'video/webm'], minduration: 0, maxduration: 120, protocols: [2, 3, 5, 6]}}] + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '273f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_NATIVE_VALID_BID = [ { bidder: 'ix', @@ -400,7 +510,7 @@ describe('IndexexchangeAdapter', function () { } }, nativeOrtbRequest: { - assets: [{id: 0, required: 0, img: {type: 1}}, {id: 1, required: 1, title: {len: 140}}, {id: 2, required: 1, data: {type: 2}}, {id: 3, required: 1, img: {type: 3}}, {id: 4, required: false, video: {mimes: ['video/mp4', 'video/webm'], minduration: 0, maxduration: 120, protocols: [2, 3, 5, 6]}}] + assets: [{ id: 0, required: 0, img: { type: 1 } }, { id: 1, required: 1, title: { len: 140 } }, { id: 2, required: 1, data: { type: 2 } }, { id: 3, required: 1, img: { type: 3 } }, { id: 4, required: false, video: { mimes: ['video/mp4', 'video/webm'], minduration: 0, maxduration: 120, protocols: [2, 3, 5, 6] } }] }, adUnitCode: 'div-gpt-ad-1460505748563-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47231', @@ -441,7 +551,7 @@ describe('IndexexchangeAdapter', function () { } }, nativeOrtbRequest: { - assets: [{id: 0, required: 0, img: {type: 1}}, {id: 1, required: 1, title: {len: 140}}, {id: 2, required: 1, data: {type: 2}}, {id: 3, required: 1, img: {type: 3}}, {id: 4, required: false, video: {mimes: ['video/mp4', 'video/webm'], minduration: 0, maxduration: 120, protocols: [2, 3, 5, 6]}}] + assets: [{ id: 0, required: 0, img: { type: 1 } }, { id: 1, required: 1, title: { len: 140 } }, { id: 2, required: 1, data: { type: 2 } }, { id: 3, required: 1, img: { type: 3 } }, { id: 4, required: false, video: { mimes: ['video/mp4', 'video/webm'], minduration: 0, maxduration: 120, protocols: [2, 3, 5, 6] } }] }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', @@ -548,7 +658,7 @@ describe('IndexexchangeAdapter', function () { } }; - const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { + const DEFAULT_VIDEO_BID_RESPONSE_WITH_XML_ADM = { cur: 'USD', id: '1aa2bb3cc4de', seatbid: [ @@ -565,7 +675,6 @@ describe('IndexexchangeAdapter', function () { mtype: 2, adm: ' Test In-Stream Video {} + render: () => { } }; bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.playerSize = [[300, 249]]; @@ -1080,7 +1172,7 @@ describe('IndexexchangeAdapter', function () { bid.nativeOrtbRequest = {} expect(spec.isBidRequestValid(bid)).to.be.false; - bid.nativeOrtbRequest = {assets: []} + bid.nativeOrtbRequest = { assets: [] } expect(spec.isBidRequestValid(bid)).to.be.false; }); }); @@ -1104,10 +1196,10 @@ describe('IndexexchangeAdapter', function () { const cloneValidBid = utils.deepClone(validBid); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, ALIAS_OPTIONS); - const payload = JSON.parse(request[0].data.r); + const payload = extractPayload(request[0]); expect(request).to.be.an('array'); expect(request).to.have.lengthOf.above(0); // should be 1 or more - expect(payload.user.eids).to.have.lengthOf(6); + expect(payload.user.eids).to.have.lengthOf(7); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); }); }); @@ -1115,7 +1207,7 @@ describe('IndexexchangeAdapter', function () { describe('buildRequestsIdentity', function () { let request; - let query; + let payload; let testCopy; beforeEach(function () { @@ -1124,7 +1216,7 @@ describe('IndexexchangeAdapter', function () { return testCopy; }; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - query = request.data; + payload = extractPayload(request); }); afterEach(function () { @@ -1137,8 +1229,6 @@ describe('IndexexchangeAdapter', function () { }); it('payload should have correct format and value (single identity partner)', function () { - const payload = JSON.parse(query.r); - expect(payload.user).to.exist; expect(payload.user.eids).to.exist; expect(payload.user.eids).to.be.an('array'); @@ -1146,7 +1236,7 @@ describe('IndexexchangeAdapter', function () { }); it('identity data in impression should have correct format and value (single identity partner)', function () { - const impression = JSON.parse(query.r).user.eids; + const impression = payload.user.eids; expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); expect(impression[0].uids[0].id).to.equal(testCopy.IdentityIp.data.uids[0].id); }); @@ -1166,8 +1256,6 @@ describe('IndexexchangeAdapter', function () { }); it('payload should have correct format and value (single identity w/ multi ids)', function () { - const payload = JSON.parse(query.r); - expect(payload.user).to.exist; expect(payload.user.eids).to.exist; expect(payload.user.eids).to.be.an('array'); @@ -1175,7 +1263,7 @@ describe('IndexexchangeAdapter', function () { }); it('identity data in impression should have correct format and value (single identity w/ multi ids)', function () { - const impression = JSON.parse(query.r).user.eids; + const impression = payload.user.eids; expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); expect(impression[0].uids).to.have.lengthOf(3); @@ -1210,8 +1298,6 @@ describe('IndexexchangeAdapter', function () { }); it('payload should have correct format and value (multiple identity partners)', function () { - const payload = JSON.parse(query.r); - expect(payload.user).to.exist; expect(payload.user.eids).to.exist; expect(payload.user.eids).to.be.an('array'); @@ -1219,7 +1305,7 @@ describe('IndexexchangeAdapter', function () { }); it('identity data in impression should have correct format and value (multiple identity partners)', function () { - const impression = JSON.parse(query.r).user.eids; + const impression = payload.user.eids; expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); expect(impression[0].uids).to.have.lengthOf(1); @@ -1242,8 +1328,7 @@ describe('IndexexchangeAdapter', function () { return undefined; }; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - query = request.data; - const payload = JSON.parse(query.r); + payload = extractPayload(request); expect(payload.user).to.exist; expect(payload.user.eids).to.not.exist; @@ -1255,8 +1340,7 @@ describe('IndexexchangeAdapter', function () { data: {} } request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - query = request.data; - const payload = JSON.parse(query.r); + payload = extractPayload(request); expect(payload.user).to.exist; expect(payload.user.eids).to.exist; @@ -1265,8 +1349,7 @@ describe('IndexexchangeAdapter', function () { it('payload should not have any user eids if identity data is pending for all partners', function () { testCopy.IdentityIp.responsePending = true; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - query = request.data; - const payload = JSON.parse(query.r); + payload = extractPayload(request); expect(payload.user).to.exist; expect(payload.user.eids).to.not.exist; @@ -1276,8 +1359,7 @@ describe('IndexexchangeAdapter', function () { testCopy.IdentityIp.responsePending = false; testCopy.IdentityIp.data = {}; request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - query = request.data; - const payload = JSON.parse(query.r); + payload = extractPayload(request); expect(payload.user).to.exist; expect(payload.user.eids).to.not.exist; @@ -1304,9 +1386,8 @@ describe('IndexexchangeAdapter', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); - - expect(payload.user.eids).to.have.lengthOf(6); + const payload = extractPayload(request); + expect(payload.user.eids).to.have.lengthOf(7); expect(payload.user.eids).to.have.deep.members(DEFAULT_USERID_PAYLOAD); }); @@ -1401,7 +1482,7 @@ describe('IndexexchangeAdapter', function () { cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); + const payload = extractPayload(request); validUserIdPayload = utils.deepClone(DEFAULT_USERID_PAYLOAD); validUserIdPayload.push({ @@ -1439,7 +1520,7 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(8); + expect(payload.user.eids).to.have.lengthOf(9); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); @@ -1480,8 +1561,8 @@ describe('IndexexchangeAdapter', function () { }] }); - const payload = JSON.parse(request.data.r); - expect(payload.user.eids).to.have.lengthOf(7); + const payload = extractPayload(request); + expect(payload.user.eids).to.have.lengthOf(8); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); }); @@ -1503,7 +1584,7 @@ describe('IndexexchangeAdapter', function () { bid.userId = DEFAULT_USERID_BID_DATA; const request = spec.buildRequests([bid], DEFAULT_OPTION)[0]; - const r = JSON.parse(request.data.r); + const r = extractPayload(request); expect(r.ext.ixdiag.userIds).to.be.an('array'); expect(r.ext.ixdiag.userIds.should.not.include('lotamePanoramaId')); @@ -1514,8 +1595,8 @@ describe('IndexexchangeAdapter', function () { describe('First party data', function () { it('should not set ixdiag.fpd value if not defined', function () { - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {ortb2: {}})[0]; - const r = JSON.parse(request.data.r); + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {} })[0]; + const r = extractPayload(request); expect(r.ext.ixdiag.fpd).to.be.undefined; }); @@ -1531,21 +1612,21 @@ describe('IndexexchangeAdapter', function () { } }; - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {ortb2})[0]; - const r = JSON.parse(request.data.r); + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2 })[0]; + const r = extractPayload(request); expect(r.ext.ixdiag.fpd).to.exist; }); - it('should set ixdiag.tmax value if it exists using tmax', function () { + it('should set ixdiag.tmax value from bidderRequest overriding global config bidderTimeout', function () { config.setConfig({ bidderTimeout: 250 }); - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; - const r = JSON.parse(request.data.r); + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { timeout: 1234 })[0]; + const r = extractPayload(request); - expect(r.ext.ixdiag.tmax).to.equal(250); + expect(r.ext.ixdiag.tmax).to.equal(1234); }); it('should not set ixdiag.tmax value if bidderTimeout is undefined', function () { @@ -1553,11 +1634,18 @@ describe('IndexexchangeAdapter', function () { bidderTimeout: null }) const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; - const r = JSON.parse(request.data.r); + const r = extractPayload(request); expect(r.ext.ixdiag.tmax).to.be.undefined }); + it('should set ixdiag.imps to number of impressions', function () { + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const r = extractPayload(request); + + expect(r.ext.ixdiag.imps).to.equal(1); + }); + it('should not send information that is not part of openRTB spec v2.5 using ortb2', function () { const ortb2 = { site: { @@ -1571,8 +1659,8 @@ describe('IndexexchangeAdapter', function () { } }; - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {ortb2})[0]; - const r = JSON.parse(request.data.r); + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2 })[0]; + const r = extractPayload(request); expect(r.site.keywords).to.exist; expect(r.site.search).to.exist; @@ -1595,46 +1683,72 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; - const request = spec.buildRequests([bid], {ortb2})[0]; - const r = JSON.parse(request.data.r); + const request = spec.buildRequests([bid], { ortb2 })[0]; + const r = extractPayload(request); expect(r.site.ref).to.exist; expect(r.site.keywords).to.be.undefined; expect(r.user).to.be.undefined; }); + it('should set gpp and gpp_sid field when defined', function () { + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {gpp: 'gpp', gpp_sid: [1]}} })[0]; + const r = extractPayload(request); + + expect(r.regs.gpp).to.equal('gpp'); + expect(r.regs.gpp_sid).to.be.an('array'); + expect(r.regs.gpp_sid).to.include(1); + }); + it('should not set gpp and gpp_sid field when not defined', function () { + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {}} })[0]; + const r = extractPayload(request); + + expect(r.regs).to.be.undefined; + }); + it('should not set gpp and gpp_sid field when fields arent strings or array defined', function () { + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {gpp: 1, gpp_sid: 'string'}} })[0]; + const r = extractPayload(request); + + expect(r.regs).to.be.undefined; + }); + it('should set gpp info from module when it exists', function () { + const options = { + gppConsent: { + gppString: 'gpp', + applicableSections: [1] + } + }; + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); + const r = extractPayload(request[0]); + + expect(r.regs.gpp).to.equal('gpp'); + expect(r.regs.gpp_sid).to.be.an('array'); + expect(r.regs.gpp_sid).to.include(1); + }); }); describe('buildRequests', function () { let request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const requestUrl = request.url; const requestMethod = request.method; - const query = request.data; + const payloadData = request.data; const bidWithoutSchain = utils.deepClone(DEFAULT_BANNER_VALID_BID); delete bidWithoutSchain[0].schain; const requestWithoutSchain = spec.buildRequests(bidWithoutSchain, DEFAULT_OPTION)[0]; - const queryWithoutSchain = requestWithoutSchain.data; + const payloadWithoutSchain = extractPayload(requestWithoutSchain); const GPID = '/19968336/some-adunit-path'; - it('request should be made to IX endpoint with GET method', function () { - expect(requestMethod).to.equal('GET'); - expect(requestUrl).to.equal(IX_SECURE_ENDPOINT); + it('request should be made to IX endpoint with POST method and siteId in query param', function () { + expect(requestMethod).to.equal('POST'); + expect(requestUrl).to.equal(IX_SECURE_ENDPOINT + '?s=' + DEFAULT_BANNER_VALID_BID[0].params.siteId); + expect(request.option.contentType).to.equal('text/plain') }); it('auction type should be set correctly', function () { - const at = JSON.parse(query.r).at; + const at = payloadData.at; expect(at).to.equal(1); }) - it('query object (version, siteID and request) should be correct', function () { - expect(query.v).to.equal(BANNER_ENDPOINT_VERSION); - expect(query.s).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); - expect(query.r).to.exist; - expect(query.ac).to.equal('j'); - expect(query.sd).to.equal(1); - expect(query.nf).not.to.exist; - }); - it('should send dfp_adunit_code in request if ortb2Imp.ext.data.adserver.adslot exists', function () { const AD_UNIT_CODE = '/19968336/some-adunit-path'; const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); @@ -1649,7 +1763,7 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); - const { ext: { dfp_ad_unit_code } } = JSON.parse(requests[0].data.r).imp[0]; + const { ext: { dfp_ad_unit_code } } = extractPayload(requests[0]).imp[0]; expect(dfp_ad_unit_code).to.equal(AD_UNIT_CODE); }); @@ -1661,7 +1775,7 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); - const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; + const { ext: { gpid } } = extractPayload(requests[0]).imp[0]; expect(gpid).to.equal(GPID); }); @@ -1673,7 +1787,7 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); - const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; + const { ext: { gpid } } = extractPayload(requests[0]).imp[0]; expect(gpid).to.equal(GPID); }); @@ -1686,7 +1800,7 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); - const imp = JSON.parse(requests[0].data.r).imp[0]; + const imp = extractPayload(requests[0]).imp[0]; expect(deepAccess(imp, 'ext.dfp_ad_unit_code')).to.not.exist; }); @@ -1704,7 +1818,7 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); - const imp = JSON.parse(requests[0].data.r).imp[0]; + const imp = extractPayload(requests[0]).imp[0]; expect(deepAccess(imp, 'ext.gpid')).to.not.exist; }); @@ -1724,13 +1838,13 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); - const imp = JSON.parse(requests[0].data.r).imp[0]; + const imp = extractPayload(requests[0]).imp[0]; expect(deepAccess(imp, 'ext.gpid')).to.equal(GPID); expect(deepAccess(imp, 'ext.dfp_ad_unit_code')).to.equal(AD_UNIT_CODE); }); it('payload should have correct format and value', function () { - const payload = JSON.parse(query.r); + const payload = payloadData; expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); expect(payload.id).to.be.a('string'); expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.page); @@ -1748,7 +1862,7 @@ describe('IndexexchangeAdapter', function () { request = spec.buildRequests(bidWithIntId, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); + const payload = extractPayload(request); expect(bidWithIntId[0].bidderRequestId).to.be.a('number'); expect(payload.id).to.equal(bidWithIntId[0].bidderRequestId.toString()); expect(payload.id).to.be.a('string'); @@ -1760,19 +1874,21 @@ describe('IndexexchangeAdapter', function () { request = spec.buildRequests(bidWithIntId, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); + const payload = extractPayload(request); expect(bidWithIntId[0].bidderRequestId).to.be.a('number'); expect(payload.id).to.equal(bidWithIntId[0].bidderRequestId.toString()); expect(payload.id).to.be.a('string'); }); it('payload should not include schain when not provided', function () { - const payload = JSON.parse(queryWithoutSchain.r); - expect(payload.source.schain).to.not.exist; // source object currently only written for schain + const payload = payloadWithoutSchain; + + const actualSchain = (((payload || {}).source || {}).ext || {}).schain; + expect(actualSchain).to.not.exist; }); it('impression should have correct format and value', function () { - const impression = JSON.parse(query.r).imp[0]; + const impression = payloadData.imp[0]; const sidValue = DEFAULT_BANNER_VALID_BID[0].params.id; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); @@ -1797,7 +1913,7 @@ describe('IndexexchangeAdapter', function () { request = spec.buildRequests([bid], DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); + const payload = extractPayload(request); payload.imp[0].banner.format.forEach((imp) => { expect(imp.ext.siteID).to.be.a('string'); }); @@ -1813,7 +1929,7 @@ describe('IndexexchangeAdapter', function () { const expectedFloor = 3.25; bid.getFloor = () => ({ floor: expectedFloor, currency }); const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); expect(impression.bidfloorcur).to.equal(currency); @@ -1825,7 +1941,7 @@ describe('IndexexchangeAdapter', function () { const expectedFloor = 3.25; bid.getFloor = () => ({ floor: expectedFloor, currency }); const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); expect(impression.bidfloorcur).to.equal(currency); @@ -1837,7 +1953,7 @@ describe('IndexexchangeAdapter', function () { bid.params.bidFloor = highFloor; bid.params.bidFloorCur = 'USD' const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.bidfloor).to.equal(highFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); @@ -1851,7 +1967,7 @@ describe('IndexexchangeAdapter', function () { const expectedFloor = highFloor; bid.getFloor = () => ({ floor: expectedFloor, currency }); const requestBidFloor = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + const impression = extractPayload(requestBidFloor).imp[0]; expect(impression.bidfloor).to.equal(highFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); @@ -1863,13 +1979,25 @@ describe('IndexexchangeAdapter', function () { bid.params.bidFloor = 50; bid.params.bidFloorCur = 'USD'; const requestBidFloor = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + const impression = extractPayload(requestBidFloor).imp[0]; expect(impression.bidfloor).to.equal(bid.params.bidFloor); expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); expect(impression.banner.format[0].ext.fl).to.equal('x'); }); + it('banner multi size impression should have bidFloor both in imp and format ext obejcts', function () { + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.params.bidFloor = 50; + bid.params.bidFloorCur = 'USD'; + const requestBidFloor = spec.buildRequests([bid], {})[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + + expect(impression.bidfloor).to.equal(bid.params.bidFloor); + expect(impression.bidfloorcur).to.equal(bid.params.bidFloorCur); + expect(impression.banner.format[0].ext.bidfloor).to.equal(50); + }); + it('missing sizes impressions should contain floors from priceFloors module ', function () { const bid = utils.deepClone(ONE_BANNER[0]); bid.mediaTypes.banner.sizes.push([500, 400]) @@ -1888,7 +2016,7 @@ describe('IndexexchangeAdapter', function () { expect(bid.getFloor.getCall(1).args[0].size[0]).to.equal(500); expect(bid.getFloor.getCall(1).args[0].size[1]).to.equal(400); - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + const impression = extractPayload(requestBidFloor).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); expect(impression.bidfloorcur).to.equal(currency); }); @@ -1918,7 +2046,7 @@ describe('IndexexchangeAdapter', function () { expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + const impression = extractPayload(requestBidFloor).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); expect(impression.bidfloorcur).to.equal(currency); }); @@ -1928,7 +2056,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.id = 50; const requestBidFloor = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + const impression = extractPayload(requestBidFloor).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); @@ -1942,7 +2070,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.id = 'abc'; const requestBidFloor = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + const impression = extractPayload(requestBidFloor).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); @@ -1970,11 +2098,49 @@ describe('IndexexchangeAdapter', function () { }); const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; + const pageUrl = extractPayload(requestWithFirstPartyData).site.page; const expectedPageUrl = DEFAULT_OPTION.refererInfo.page + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; expect(pageUrl).to.equal(expectedPageUrl); }); + it('should set device sua if available in fpd and request size does not exceed limit', function () { + const ortb2 = { + device: { + sua: { + platform: { + brand: 'macOS', + version: [ '12', '6', '1' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '107', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '107', '0', '5249', '119' ] + }, + ], + mobile: 0, + model: '' + } + }}; + + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2 })[0]; + const payload = extractPayload(request); + expect(payload.device.sua.platform.brand).to.equal('macOS') + expect(payload.device.sua.mobile).to.equal(0) + }); + + it('should not set device sua if not available in fpd', function () { + const ortb2 = { + device: {}}; + + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2 })[0]; + const payload = extractPayload(request); + expect(payload.device).to.be.undefined + }); + it('should not set first party data if it is not an object', function () { config.setConfig({ ix: { @@ -1983,7 +2149,7 @@ describe('IndexexchangeAdapter', function () { }); const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestFirstPartyDataNumber.data.r).site.page; + const pageUrl = extractPayload(requestFirstPartyDataNumber).site.page; expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.page); }); @@ -1994,7 +2160,7 @@ describe('IndexexchangeAdapter', function () { }); const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; + const pageUrl = extractPayload(requestWithoutConfig).site.page; expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.page); expect(requestWithoutConfig.data.t).to.be.undefined; @@ -2002,13 +2168,13 @@ describe('IndexexchangeAdapter', function () { it('should not set first party or timeout if it is setConfig is not called', function () { const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; - const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; + const pageUrl = extractPayload(requestWithoutConfig).site.page; expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.page); expect(requestWithoutConfig.data.t).to.be.undefined; }); - it('should set timeout if publisher set it through setConfig', function () { + it('should no longer set timeout even if publisher set it through setConfig', function () { config.setConfig({ ix: { timeout: 500 @@ -2016,10 +2182,10 @@ describe('IndexexchangeAdapter', function () { }); const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0]; - expect(requestWithTimeout.data.t).to.equal(500); + expect(requestWithTimeout.data.t).to.be.undefined; }); - it('should set timeout if timeout is a string', function () { + it('should no longer set timeout even if timeout is a string', function () { config.setConfig({ ix: { timeout: '500' @@ -2034,11 +2200,10 @@ describe('IndexexchangeAdapter', function () { describe('request should contain both banner and video requests', function () { const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]], {}); it('should have banner request', () => { - const bannerImpression = JSON.parse(request[0].data.r).imp[0]; + const bannerImpression = extractPayload(request[0]).imp[0]; const sidValue = DEFAULT_BANNER_VALID_BID[0].params.id; - expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); - expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); expect(bannerImpression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(bannerImpression.banner.format).to.be.length(2); @@ -2055,9 +2220,8 @@ describe('IndexexchangeAdapter', function () { }); it('should have video request', () => { - const videoImpression = JSON.parse(request[1].data.r).imp[0]; + const videoImpression = extractPayload(request[1]).imp[0]; - expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); expect(videoImpression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); expect(videoImpression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); expect(videoImpression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); @@ -2068,11 +2232,10 @@ describe('IndexexchangeAdapter', function () { const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_NATIVE_VALID_BID[0]]); it('should have banner request', () => { - const bannerImpression = JSON.parse(request[0].data.r).imp[0]; + const bannerImpression = extractPayload(request[0]).imp[0]; const sidValue = DEFAULT_BANNER_VALID_BID[0].params.id; - expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); - expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); expect(bannerImpression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(bannerImpression.banner.format).to.be.length(2); @@ -2089,7 +2252,7 @@ describe('IndexexchangeAdapter', function () { }); it('should have native request', () => { - const nativeImpression = JSON.parse(request[1].data.r).imp[0]; + const nativeImpression = extractPayload(request[1]).imp[0]; expect(request[1].data.hasOwnProperty('v')).to.equal(false); expect(nativeImpression.id).to.equal(DEFAULT_NATIVE_VALID_BID[0].bidId); @@ -2121,8 +2284,6 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); expect(requests).to.be.an('array'); expect(requests).to.have.lengthOf(2); - expect(requests[0].data.sn).to.be.equal(0); - expect(requests[1].data.sn).to.be.equal(1); }); it('6 ad units should generate only 4 requests', function () { @@ -2156,10 +2317,7 @@ describe('IndexexchangeAdapter', function () { for (var i = 0; i < requests.length; i++) { const reqSize = `${requests[i].url}?${utils.parseQueryStringParameters(requests[i].data)}`.length; expect(reqSize).to.be.lessThan(8000); - let payload = JSON.parse(requests[i].data.r); - if (requests.length > 1) { - expect(requests[i].data.sn).to.equal(i); - } + let payload = extractPayload(requests[i]); expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); } }); @@ -2184,7 +2342,7 @@ describe('IndexexchangeAdapter', function () { expect(requests).to.be.an('array'); expect(requests).to.have.lengthOf(1); - const impressions = JSON.parse(requests[0].data.r).imp; + const impressions = extractPayload(requests[0]).imp; expect(impressions).to.be.an('array'); expect(impressions).to.have.lengthOf(3); expect(requests[0].data.sn).to.be.undefined; @@ -2200,7 +2358,7 @@ describe('IndexexchangeAdapter', function () { bid2.params.bidId = '2b3c4d5e'; const request = spec.buildRequests([bid, bid2], DEFAULT_OPTION)[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; const sidValue = bid.params.id; expect(impression.id).to.equal(bid.bidId); @@ -2230,7 +2388,7 @@ describe('IndexexchangeAdapter', function () { const bids = [DEFAULT_BANNER_VALID_BID[0], bid]; const request = spec.buildRequests(bids, DEFAULT_OPTION)[0]; - const impressions = JSON.parse(request.data.r).imp; + const impressions = extractPayload(request).imp; expect(impressions).to.be.an('array'); expect(impressions).to.have.lengthOf(2); expect(request.data.sn).to.be.undefined; @@ -2255,7 +2413,7 @@ describe('IndexexchangeAdapter', function () { it('request should not contain the extra video ad sizes that IX is not configured for', function () { const request = spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION); - const impressions = JSON.parse(request[0].data.r).imp; + const impressions = extractPayload(request[0]).imp; expect(impressions).to.be.an('array'); expect(impressions).to.have.lengthOf(1); @@ -2268,7 +2426,7 @@ describe('IndexexchangeAdapter', function () { const requests = spec.buildRequests([bid1, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); - const impressions = JSON.parse(requests[0].data.r).imp; + const impressions = extractPayload(requests[0]).imp; expect(impressions).to.be.an('array'); expect(impressions).to.have.lengthOf(1); @@ -2278,24 +2436,15 @@ describe('IndexexchangeAdapter', function () { describe('buildRequestVideo', function () { const request = spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION); - const query = request[0].data; - - it('query object (version, siteID and request) should be correct', function () { - expect(query.v).to.equal(VIDEO_ENDPOINT_VERSION); - expect(query.s).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId); - expect(query.r).to.exist; - expect(query.ac).to.equal('j'); - expect(query.sd).to.equal(1); - expect(query.nf).to.equal(1); - }); + const payloadData = request[0].data; it('auction type should be set correctly', function () { - const at = JSON.parse(query.r).at; + const at = payloadData.at; expect(at).to.equal(1); }) it('impression should have correct format and value', function () { - const impression = JSON.parse(query.r).imp[0]; + const impression = payloadData.imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); expect(impression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); @@ -2314,7 +2463,7 @@ describe('IndexexchangeAdapter', function () { bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.placement = 2; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); expect(impression.video.placement).to.equal(2); @@ -2324,7 +2473,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.params.id = 50; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.ext.sid).to.equal('50'); }); @@ -2333,7 +2482,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'instream'; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); expect(impression.video.placement).to.equal(1); @@ -2343,7 +2492,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); expect(impression.video.placement).to.equal(4); @@ -2353,7 +2502,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'not-valid'; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.video.placement).to.be.undefined; }); @@ -2363,7 +2512,7 @@ describe('IndexexchangeAdapter', function () { bid.mediaTypes.video.protocols = [1]; bid.mediaTypes.video.mimes = ['video/override']; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.video.protocols[0]).to.equal(2); expect(impression.video.mimes[0]).to.not.equal('video/override'); @@ -2374,7 +2523,7 @@ describe('IndexexchangeAdapter', function () { bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.random = true; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.video.random).to.not.exist; }); @@ -2389,7 +2538,7 @@ describe('IndexexchangeAdapter', function () { bid.mediaTypes.video.api = 2; bid.mediaTypes.video.pos = 0; const request = spec.buildRequests([bid], {})[0]; - const impression = JSON.parse(request.data.r).imp[0]; + const impression = extractPayload(request).imp[0]; expect(impression.video.protocols[0]).to.equal(6); expect(impression.video.api).to.equal(2); @@ -2406,15 +2555,14 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(validBids, DEFAULT_OPTION); - const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; + const { ext: { gpid } } = extractPayload(requests[0]).imp[0]; expect(gpid).to.equal(GPID); }); it('should build video request when if video obj is not provided at params level', () => { const request = spec.buildRequests([DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0]], {}); - const videoImpression = JSON.parse(request[0].data.r).imp[0]; + const videoImpression = extractPayload(request[0]).imp[0]; - expect(JSON.parse(request[0].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); expect(videoImpression.id).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].bidId); expect(videoImpression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].mediaTypes.video.playerSize[0][0]); expect(videoImpression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].mediaTypes.video.playerSize[0][1]); @@ -2428,23 +2576,18 @@ describe('IndexexchangeAdapter', function () { }; const request = spec.buildRequests([bid]); - const videoImpression = JSON.parse(request[0].data.r).imp[0]; + const videoImpression = extractPayload(request[0]).imp[0]; expect(videoImpression.video.placement).to.eq(5); }) }); describe('buildRequestNative', function () { - it('should build request with expected params', function() { - const request = spec.buildRequests(DEFAULT_NATIVE_VALID_BID, DEFAULT_OPTION); - const query = request[0].data; + it('should build request with expected params', function () { + const request = spec.buildRequests(DEFAULT_NATIVE_VALID_BID, DEFAULT_OPTION)[0]; - expect(query.hasOwnProperty('v')).to.equal(false); - expect(query.s).to.equal(DEFAULT_NATIVE_VALID_BID[0].params.siteId); - expect(query.r).to.exist; - expect(query.ac).to.equal('j'); - expect(query.sd).to.equal(1); - expect(JSON.parse(query.r).at).to.equal(1); + expect(request.data).to.exist; + expect(request.method).to.equal('POST') }); it('should send gpid in request if ortb2Imp.ext.gpid exists', function () { @@ -2456,40 +2599,40 @@ describe('IndexexchangeAdapter', function () { } }; const requests = spec.buildRequests(bids, DEFAULT_OPTION); - const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; + const { ext: { gpid } } = extractPayload(requests[0]).imp[0]; expect(gpid).to.equal(GPID); }); - it('should build request with required asset properties with default values', function() { + it('should build request with required asset properties with default values', function () { const request = spec.buildRequests(DEFAULT_NATIVE_VALID_BID, DEFAULT_OPTION); - const nativeImpression = JSON.parse(request[0].data.r).imp[0]; + const nativeImpression = extractPayload(request[0]).imp[0]; expect(request[0].data.hasOwnProperty('v')).to.equal(false); expect(nativeImpression.id).to.equal(DEFAULT_NATIVE_VALID_BID[0].bidId); expect(nativeImpression.native).to.deep.equal(DEFAULT_NATIVE_IMP); }); - it('should set imp.ext.sid for native imps if params.id exist', function() { + it('should set imp.ext.sid for native imps if params.id exist', function () { const bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID); bid[0].params.id = 'abc' const request = spec.buildRequests(bid, DEFAULT_OPTION); - const nativeImpression = JSON.parse(request[0].data.r).imp[0]; + const nativeImpression = extractPayload(request[0]).imp[0]; expect(nativeImpression.id).to.equal(DEFAULT_NATIVE_VALID_BID[0].bidId); expect(nativeImpression.ext.sid).to.equal('abc'); }); - it('should build request with given asset properties', function() { + it('should build request with given asset properties', function () { let bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID) bid[0].nativeOrtbRequest = { - assets: [{id: 0, required: 0, title: {len: 140}}, {id: 1, required: 0, video: {mimes: ['javascript'], minduration: 10, maxduration: 60, protocols: [1]}}] + assets: [{ id: 0, required: 0, title: { len: 140 } }, { id: 1, required: 0, video: { mimes: ['javascript'], minduration: 10, maxduration: 60, protocols: [1] } }] } const request = spec.buildRequests(bid, DEFAULT_OPTION); - const nativeImpression = JSON.parse(request[0].data.r).imp[0]; - expect(nativeImpression.native).to.deep.equal({request: '{"assets":[{"id":0,"required":0,"title":{"len":140}},{"id":1,"required":0,"video":{"mimes":["javascript"],"minduration":10,"maxduration":60,"protocols":[1]}}],"eventtrackers":[{"event":1,"methods":[1,2]}],"privacy":1}', ver: '1.2'}); + const nativeImpression = extractPayload(request[0]).imp[0]; + expect(nativeImpression.native).to.deep.equal({ request: '{"assets":[{"id":0,"required":0,"title":{"len":140}},{"id":1,"required":0,"video":{"mimes":["javascript"],"minduration":10,"maxduration":60,"protocols":[1]}}],"eventtrackers":[{"event":1,"methods":[1,2]}],"privacy":1}', ver: '1.2' }); }); - it('should build request with all possible Prebid asset properties', function() { + it('should build request with all possible Prebid asset properties', function () { let bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID) bid[0].nativeOrtbRequest = { 'ver': '1.2', @@ -2614,17 +2757,16 @@ describe('IndexexchangeAdapter', function () { ] } const request = spec.buildRequests(bid, DEFAULT_OPTION); - const nativeImpression = JSON.parse(request[0].data.r).imp[0]; - expect(nativeImpression.native).to.deep.equal({request: '{"ver":"1.2","assets":[{"id":0,"required":0,"title":{"len":140}},{"id":1,"required":0,"data":{"type":2}},{"id":2,"required":0,"data":{"type":10}},{"id":3,"required":0,"data":{"type":1}},{"id":4,"required":0,"img":{"type":1}},{"id":5,"required":0,"img":{"type":3}},{"id":6,"required":0},{"id":7,"required":0,"data":{"type":11}},{"id":8,"required":0},{"id":9,"required":0},{"id":10,"required":0,"data":{"type":12}},{"id":11,"required":0,"data":{"type":3}},{"id":12,"required":0,"data":{"type":5}},{"id":13,"required":0,"data":{"type":4}},{"id":14,"required":0,"data":{"type":6}},{"id":15,"required":0,"data":{"type":7}},{"id":16,"required":0,"data":{"type":9}},{"id":17,"required":0,"data":{"type":8}}],"eventtrackers":[{"event":1,"methods":[1,2]}],"privacy":1}', ver: '1.2'}); + const nativeImpression = extractPayload(request[0]).imp[0]; + expect(nativeImpression.native).to.deep.equal({ request: '{"ver":"1.2","assets":[{"id":0,"required":0,"title":{"len":140}},{"id":1,"required":0,"data":{"type":2}},{"id":2,"required":0,"data":{"type":10}},{"id":3,"required":0,"data":{"type":1}},{"id":4,"required":0,"img":{"type":1}},{"id":5,"required":0,"img":{"type":3}},{"id":6,"required":0},{"id":7,"required":0,"data":{"type":11}},{"id":8,"required":0},{"id":9,"required":0},{"id":10,"required":0,"data":{"type":12}},{"id":11,"required":0,"data":{"type":3}},{"id":12,"required":0,"data":{"type":5}},{"id":13,"required":0,"data":{"type":4}},{"id":14,"required":0,"data":{"type":6}},{"id":15,"required":0,"data":{"type":7}},{"id":16,"required":0,"data":{"type":9}},{"id":17,"required":0,"data":{"type":8}}],"eventtrackers":[{"event":1,"methods":[1,2]}],"privacy":1}', ver: '1.2' }); }) }); describe('buildRequestMultiFormat', function () { it('only banner bidder params set', function () { const request = spec.buildRequests(DEFAULT_MULTIFORMAT_BANNER_VALID_BID, {}) - const bannerImpression = JSON.parse(request[0].data.r).imp[0]; - expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); - expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + const bannerImpression = extractPayload(request[0]).imp[0]; + expect(extractPayload(request[0]).imp).to.have.lengthOf(1); expect(bannerImpression.id).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].bidId); expect(bannerImpression.banner.format[0].w).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[0]); expect(bannerImpression.banner.format[0].h).to.equal(DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0].params.size[1]); @@ -2633,9 +2775,8 @@ describe('IndexexchangeAdapter', function () { describe('only video bidder params set', function () { it('should generate video impression', function () { const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID, {}); - const videoImp = JSON.parse(request[1].data.r).imp[0]; - expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); - expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + const videoImp = extractPayload(request[1]).imp[0]; + expect(extractPayload(request[1]).imp).to.have.lengthOf(1); expect(videoImp.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); @@ -2647,10 +2788,9 @@ describe('IndexexchangeAdapter', function () { const request = spec.buildRequests(bids, {}); it('should return valid banner requests', function () { - const impressions = JSON.parse(request[0].data.r).imp; + const impressions = extractPayload(request[0]).imp; expect(impressions).to.have.lengthOf(2); - expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); impressions.map((impression, index) => { const bid = bids[index]; @@ -2670,17 +2810,16 @@ describe('IndexexchangeAdapter', function () { }); it('should return valid banner and video requests', function () { - const videoImpression = JSON.parse(request[1].data.r).imp[0]; + const videoImpression = extractPayload(request[1]).imp[0]; - expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); - expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(extractPayload(request[1]).imp).to.have.lengthOf(1); expect(videoImpression.id).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].bidId); expect(videoImpression.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0][0]); expect(videoImpression.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].mediaTypes.video.playerSize[0][1]); }); it('should contain all correct IXdiag properties', function () { - const diagObj = JSON.parse(request[0].data.r).ext.ixdiag; + const diagObj = extractPayload(request[0]).ext.ixdiag; expect(diagObj.iu).to.equal(0); expect(diagObj.nu).to.equal(0); expect(diagObj.ou).to.equal(2); @@ -2694,9 +2833,51 @@ describe('IndexexchangeAdapter', function () { expect(diagObj.adunitcode).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].adUnitCode) }); }); + + describe('siteId overrides', function () { + it('should use siteId override', function () { + const validBids = DEFAULT_MULTIFORMAT_VALID_BID; + const request = spec.buildRequests(validBids, {}); + const bannerImps = request[0].data.imp[0]; + const videoImps = request[1].data.imp[0]; + const nativeImps = request[2].data.imp[0]; + expect(videoImps.ext.siteID).to.equal('1111'); + bannerImps.banner.format.map(({ ext }) => { + expect(ext.siteID).to.equal('2222'); + }); + expect(nativeImps.ext.siteID).to.equal('3333'); + }); + + it('should use default siteId if overrides are not provided', function () { + const validBids = DEFAULT_MULTIFORMAT_VALID_BID; + delete validBids[0].params.banner; + delete validBids[0].params.video; + delete validBids[0].params.native; + const request = spec.buildRequests(validBids, {}); + const bannerImps = request[0].data.imp[0]; + const videoImps = request[1].data.imp[0]; + const nativeImps = request[2].data.imp[0]; + expect(videoImps.ext.siteID).to.equal('456'); + bannerImps.banner.format.map(({ ext }) => { + expect(ext.siteID).to.equal('456'); + }); + expect(nativeImps.ext.siteID).to.equal('456'); + }); + }); }); describe('interpretResponse', function () { + // generate bidderRequest with real buildRequest logic for intepretResponse testing + let bannerBidderRequest + let videoBidderRequest + let nativeBidderRequest + + beforeEach(() => { + bannerBidderRequest = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0] + videoBidderRequest = spec.buildRequests(DEFAULT_VIDEO_VALID_BID_MEDIUM_SIZE, {})[0] + nativeBidderRequest = spec.buildRequests(DEFAULT_NATIVE_VALID_BID, {})[0] + }); + it('should get correct bid response for banner ad', function () { const expectedParse = [ { @@ -2718,7 +2899,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, bannerBidderRequest); expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -2742,7 +2923,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN }, bannerBidderRequest); expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -2769,7 +2950,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: bidResponse }, bannerBidderRequest); }); it('should set Japanese price correctly', function () { @@ -2795,7 +2976,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: bidResponse }, bannerBidderRequest); expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -2824,7 +3005,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: bidResponse }, bannerBidderRequest); expect(result[0].dealId).to.equal(expectedParse[0].dealId); }); @@ -2851,7 +3032,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: bidResponse }, bannerBidderRequest); expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -2879,7 +3060,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: bidResponse }, bannerBidderRequest); expect(result[0].dealId).to.deep.equal(expectedParse[0].dealId); }); @@ -2916,7 +3097,7 @@ describe('IndexexchangeAdapter', function () { } ]; const result = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: ONE_VIDEO + data: videoBidderRequest.data, validBidRequests: ONE_VIDEO }); expect(result[0]).to.deep.equal(expectedParse[0]); @@ -2924,14 +3105,14 @@ describe('IndexexchangeAdapter', function () { it('should set bid[].renderer if renderer not defined at mediaType.video level', function () { const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID + data: videoBidderRequest.data, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID }); expect(bid[0].renderer).to.exist; }); it('should set renderer URL by parsing video response', function () { const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID + data: videoBidderRequest.data, validBidRequests: DEFAULT_MULTIFORMAT_BANNER_VALID_BID }); expect(bid[0].renderer.url).to.equal(DEFAULT_VIDEO_BID_RESPONSE.ext.videoplayerurl); }); @@ -2940,10 +3121,10 @@ describe('IndexexchangeAdapter', function () { let outstreamAdUnit = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID); outstreamAdUnit[0].mediaTypes.video.renderer = { url: 'test', - render: function() {} + render: function () { } }; const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + data: videoBidderRequest.data, validBidRequests: outstreamAdUnit }); expect(bid[0].renderer).to.be.undefined; }); @@ -2952,10 +3133,10 @@ describe('IndexexchangeAdapter', function () { let outstreamAdUnit = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID); outstreamAdUnit[0].renderer = { url: 'test', - render: function() {} + render: function () { } }; const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + data: videoBidderRequest.data, validBidRequests: outstreamAdUnit }); expect(bid[0].renderer).to.be.undefined; }); @@ -2966,7 +3147,7 @@ describe('IndexexchangeAdapter', function () { url: 'test' }; const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + data: videoBidderRequest.data, validBidRequests: outstreamAdUnit }); expect(bid[0].renderer).to.exist; }); @@ -2975,11 +3156,11 @@ describe('IndexexchangeAdapter', function () { let outstreamAdUnit = utils.deepClone(DEFAULT_MULTIFORMAT_BANNER_VALID_BID); outstreamAdUnit[0].mediaTypes.video.renderer = { url: 'test', - render: function() {}, + render: function () { }, backupOnly: true }; const bid = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: outstreamAdUnit + data: videoBidderRequest.data, validBidRequests: outstreamAdUnit }); expect(bid[0].renderer).to.exist; }); @@ -3016,20 +3197,61 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET }, { - data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: ONE_VIDEO + const result = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE_WITH_XML_ADM }, { + data: videoBidderRequest.data, validBidRequests: ONE_VIDEO }); expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should not set vastxml when vasturl is present and when mtype is 2 (video)', function () { + const expectedParse = [ + { + requestId: '1a2b3c4e', + cpm: 1.1, + creativeId: '12346', + mediaType: 'video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [ + 400, + 100 + ] + ] + } + }, + width: 640, + height: 480, + currency: 'USD', + ttl: 3600, + netRevenue: true, + vastUrl: 'www.abcd.com/vast', + meta: { + networkId: 51, + brandId: 303326, + brandName: 'OECTB', + advertiserDomains: ['www.abcd.com'] + } + } + ]; + let bid_response = DEFAULT_VIDEO_BID_RESPONSE_WITH_XML_ADM; + bid_response.seatbid[0].bid[0].ext['vasturl'] = 'www.abcd.com/vast'; + const result = spec.interpretResponse({ body: bid_response }, { + data: videoBidderRequest.data, validBidRequests: ONE_VIDEO + }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + it('bidrequest should not have page if options is undefined', function () { const options = {}; const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + const requestWithoutreferInfo = extractPayload(validBidWithoutreferInfo[0]); + const expectedURL = IX_SECURE_ENDPOINT + '?s=' + DEFAULT_BANNER_VALID_BID[0].params.siteId expect(requestWithoutreferInfo.site.page).to.be.undefined; - expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + expect(validBidWithoutreferInfo[0].url).to.equal(expectedURL); }); it('bidrequest should not have page if options.refererInfo is an empty object', function () { @@ -3037,10 +3259,11 @@ describe('IndexexchangeAdapter', function () { refererInfo: {} }; const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + const requestWithoutreferInfo = extractPayload(validBidWithoutreferInfo[0]); + const expectedURL = IX_SECURE_ENDPOINT + '?s=' + DEFAULT_BANNER_VALID_BID[0].params.siteId expect(requestWithoutreferInfo.site.page).to.be.undefined; - expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + expect(validBidWithoutreferInfo[0].url).to.equal(expectedURL); }); it('bidrequest should sent to secure endpoint if page url is secure', function () { @@ -3050,10 +3273,11 @@ describe('IndexexchangeAdapter', function () { } }; const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); + const requestWithoutreferInfo = extractPayload(validBidWithoutreferInfo[0]); + const expectedURL = IX_SECURE_ENDPOINT + '?s=' + DEFAULT_BANNER_VALID_BID[0].params.siteId expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.page); - expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); + expect(validBidWithoutreferInfo[0].url).to.equal(expectedURL); }); it('should set bid[].ttl to seatbid[].bid[].exp value from response', function () { @@ -3061,16 +3285,16 @@ describe('IndexexchangeAdapter', function () { const VIDEO_RESPONSE_WITH_EXP = utils.deepClone(DEFAULT_VIDEO_BID_RESPONSE); VIDEO_RESPONSE_WITH_EXP.seatbid[0].bid[0].exp = 200; BANNER_RESPONSE_WITH_EXP.seatbid[0].bid[0].exp = 100; - const bannerResult = spec.interpretResponse({ body: BANNER_RESPONSE_WITH_EXP }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); - const videoResult = spec.interpretResponse({ body: VIDEO_RESPONSE_WITH_EXP }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const bannerResult = spec.interpretResponse({ body: BANNER_RESPONSE_WITH_EXP }, bannerBidderRequest); + const videoResult = spec.interpretResponse({ body: VIDEO_RESPONSE_WITH_EXP }, videoBidderRequest); expect(bannerResult[0].ttl).to.equal(100); expect(videoResult[0].ttl).to.equal(200); }); it('should default bid[].ttl if seat[].bid[].exp is not in the resposne', function () { - const bannerResult = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); - const videoResult = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const bannerResult = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, bannerBidderRequest); + const videoResult = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, videoBidderRequest); expect(bannerResult[0].ttl).to.equal(300); expect(videoResult[0].ttl).to.equal(3600); @@ -3162,7 +3386,7 @@ describe('IndexexchangeAdapter', function () { ttl: 3600 } ]; - const result = spec.interpretResponse({ body: DEFAULT_NATIVE_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + const result = spec.interpretResponse({ body: DEFAULT_NATIVE_BID_RESPONSE }, nativeBidderRequest); expect(result[0]).to.deep.equal(expectedParse[0]); }); }); @@ -3170,7 +3394,7 @@ describe('IndexexchangeAdapter', function () { describe('bidrequest consent', function () { it('should have consent info if gdprApplies and consentString exist', function () { const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); - const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + const requestWithConsent = extractPayload(validBidWithConsent[0]); expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); @@ -3184,7 +3408,7 @@ describe('IndexexchangeAdapter', function () { } }; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + const requestWithConsent = extractPayload(validBidWithConsent[0]); expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.user).to.be.undefined; @@ -3198,7 +3422,7 @@ describe('IndexexchangeAdapter', function () { } }; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + const requestWithConsent = extractPayload(validBidWithConsent[0]); expect(requestWithConsent.regs).to.be.undefined; expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); @@ -3207,7 +3431,7 @@ describe('IndexexchangeAdapter', function () { it('should not have consent info if options.gdprConsent is undefined', function () { const options = {}; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + const requestWithConsent = extractPayload(validBidWithConsent[0]); expect(requestWithConsent.regs).to.be.undefined; expect(requestWithConsent.user).to.be.undefined; @@ -3218,7 +3442,7 @@ describe('IndexexchangeAdapter', function () { uspConsent: '1YYN' }; const validBidWithUspConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithUspConsent = JSON.parse(validBidWithUspConsent[0].data.r); + const requestWithUspConsent = extractPayload(validBidWithUspConsent[0]); expect(requestWithUspConsent.regs.ext.us_privacy).to.equal('1YYN'); }); @@ -3226,7 +3450,7 @@ describe('IndexexchangeAdapter', function () { it('should not have us_privacy if uspConsent undefined', function () { const options = {}; const validBidWithUspConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithUspConsent = JSON.parse(validBidWithUspConsent[0].data.r); + const requestWithUspConsent = extractPayload(validBidWithUspConsent[0]); expect(requestWithUspConsent.regs).to.be.undefined; }); @@ -3240,7 +3464,7 @@ describe('IndexexchangeAdapter', function () { uspConsent: '1YYN' }; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + const requestWithConsent = extractPayload(validBidWithConsent[0]); expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.regs.ext.us_privacy).to.equal('1YYN'); }); @@ -3254,7 +3478,7 @@ describe('IndexexchangeAdapter', function () { }; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + const requestWithConsent = extractPayload(validBidWithConsent[0]); expect(requestWithConsent.user.ext.consented_providers_settings.consented_providers).to.equal('1~1.35.41.101'); expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); }); @@ -3267,7 +3491,7 @@ describe('IndexexchangeAdapter', function () { }; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); + const requestWithConsent = extractPayload(validBidWithConsent[0]); expect(utils.deepAccess(requestWithConsent, 'user.ext.consented_providers_settings')).to.not.exist; expect(utils.deepAccess(requestWithConsent, 'user.ext.consent')).to.not.exist; }); @@ -3275,21 +3499,21 @@ describe('IndexexchangeAdapter', function () { it('should set coppa to 1 in config when enabled', () => { config.setConfig({ coppa: true }) const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); - const r = JSON.parse(bid[0].data.r); + const r = extractPayload(bid[0]); expect(r.regs.coppa).to.equal(1); }); it('should not set coppa in config when disabled', () => { config.setConfig({ coppa: false }) const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); - const r = JSON.parse(bid[0].data.r); + const r = extractPayload(bid[0]); expect(r.regs.coppa).to.be.undefined; }); it('should not set coppa when not specified in config', () => { config.resetConfig(); const bid = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); - const r = JSON.parse(bid[0].data.r); + const r = extractPayload(bid[0]); expect(r.regs.coppa).to.be.undefined; }); @@ -3391,125 +3615,62 @@ describe('IndexexchangeAdapter', function () { expect(FEATURE_TOGGLES.featureToggles).to.deep.equal({}); }); - it('should create POST request when pbjs_enable_post feature is active', () => { + it('6 ad units should generate only 1 request if buildRequestV2 FT is enabled', function () { sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_enable_post = { + serverResponse.body.ext.features.pbjs_use_buildRequestV2 = { activated: true }; FEATURE_TOGGLES.setFeatureToggles(serverResponse); - const builtRequests = { - banner: { - request: spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0], - siteId: DEFAULT_BANNER_VALID_BID[0].params.siteId - }, - video: { - request: spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION)[0], - siteId: DEFAULT_VIDEO_VALID_BID[0].params.siteId - }, - native: { - request: spec.buildRequests(DEFAULT_NATIVE_VALID_BID, DEFAULT_OPTION)[0], - siteId: DEFAULT_NATIVE_VALID_BID[0].params.siteId - } - }; - Object.keys(builtRequests).forEach((reqType) => { - const request = builtRequests[reqType].request; - const siteId = builtRequests[reqType].siteId; - expect(request.method).to.equal('POST'); - expect(request.option.contentType).to.equal('text/plain'); - expect(request.url).to.be.equal(`${IX_SECURE_ENDPOINT}?s=${siteId}`); - expect(request.data.r).to.be.undefined; - expect(request.data.ac).to.be.undefined; - expect(request.data.sd).to.be.undefined; - expect(request.data.v).to.be.undefined; - expect(request.data.nf).to.be.undefined; - }); - }); + const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + bid1.params.siteId = '121'; + bid1.adUnitCode = 'div-gpt-1' + bid1.transactionId = 'tr1'; + bid1.bidId = '2f6g5s5e'; - it('should create GET request when pbjs_enable_post feature is disabled', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - serverResponse.body.ext.features.pbjs_enable_post = { - activated: false - }; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); - const builtRequests = { - banner: { - request: spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0], - siteId: DEFAULT_BANNER_VALID_BID[0].params.siteId - }, - video: { - request: spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION)[0], - siteId: DEFAULT_VIDEO_VALID_BID[0].params.siteId - }, - native: { - request: spec.buildRequests(DEFAULT_NATIVE_VALID_BID, DEFAULT_OPTION)[0], - siteId: DEFAULT_NATIVE_VALID_BID[0].params.siteId - } - }; + const bid2 = utils.deepClone(bid1); + bid2.transactionId = 'tr2'; - Object.keys(builtRequests).forEach((reqType) => { - const request = builtRequests[reqType].request; - const siteId = builtRequests[reqType].siteId; - expect(request.method).to.equal('GET'); - expect(request.url).to.equal(IX_SECURE_ENDPOINT); - expect(request.data.r).to.exist; - expect(request.data.ac).to.equal('j'); - expect(request.data.sd).to.equal(1); - expect(request.data.s).to.equal(siteId); - if (reqType == 'video') { - expect(request.data.nf).to.equal(1); - } + const bid3 = utils.deepClone(bid1); + bid3.transactionId = 'tr3'; - if (reqType === 'native') { - expect(request.data.v).to.be.undefined; - } - }); + const bid4 = utils.deepClone(bid1); + bid4.transactionId = 'tr4'; + + const bid5 = utils.deepClone(bid1); + bid5.transactionId = 'tr5'; + + const bid6 = utils.deepClone(bid1); + bid6.transactionId = 'tr6'; + + const requests = spec.buildRequests([bid1, bid2, bid3, bid4, bid5, bid6], DEFAULT_OPTION); + + expect(requests).to.be.an('array'); + // buildRequestv2 enabled causes only 1 requests to get generated. + expect(requests).to.have.lengthOf(1); + for (let request of requests) { + expect(request.method).to.equal('POST'); + } }); - it('should interpertResponse correctly when pbjs_enable_post feature is active', function () { - serverResponse.body.ext.features.pbjs_enable_post = { + it('1 request with 2 ad units, buildRequestV2 enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + serverResponse.body.ext.features.pbjs_use_buildRequestV2 = { activated: true }; FEATURE_TOGGLES.setFeatureToggles(serverResponse); - const postBiddderRequestData = { - id: '345', - imp: [ - { - id: '1a2b3c4e', - banner: { - w: 300, - h: 250, - placement: 1 - } - } - ], - site: { - ref: 'https://ref.com/ref.html', - page: 'https://page.com' - }, - }; - const expectedParse = [ - { - requestId: '1a2b3c4d', - cpm: 1, - creativeId: '12345', - width: 300, - height: 250, - mediaType: 'banner', - ad: '', - currency: 'USD', - ttl: 300, - netRevenue: true, - meta: { - networkId: 50, - brandId: 303325, - brandName: 'OECTA', - advertiserDomains: ['www.abc.com'] - } - } - ]; - const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: postBiddderRequestData, validBidRequests: [] }); - expect(result[0]).to.deep.equal(expectedParse[0]); + + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; + bid.params.siteId = '124'; + bid.adUnitCode = 'div-gpt-1' + bid.transactionId = '152e36d1-1241-4242-t35e-y1dv34d12315'; + bid.bidId = '2f6g5s5e'; + + const requests = spec.buildRequests([bid, DEFAULT_BANNER_VALID_BID[0]], DEFAULT_OPTION); + expect(requests).to.be.an('array'); + expect(requests).to.have.lengthOf(1); }); }); @@ -3628,7 +3789,7 @@ describe('IndexexchangeAdapter', function () { }; expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid], {ortb2}); + spec.buildRequests([bid], { ortb2 }); expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE]: 2 } }); }); @@ -3682,30 +3843,24 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(request)).to.be.true; const data = { - ...utils.deepClone(DEFAULT_BIDDER_REQUEST_DATA[0]), - r: JSON.stringify({ - id: '345', - imp: [ - { - id: '1a2b3c4e', - } - ], - ext: { - ixdiag: { - err: { - '4': 8 - } + id: '345', + imp: [ + { + id: '1a2b3c4e', + } + ], + ext: { + ixdiag: { + err: { + '4': 8 } } - }), + } }; - const validBidRequests = [ - DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], - DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0] - ]; + const validBidRequest = DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0]; - spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data, validBidRequests }); + spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data, validBidRequest }); expect(localStorageValues[key]).to.be.undefined; }); diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index 0c999f1cd51..c354de3c3ac 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -71,6 +71,7 @@ describe('jixie Adapter', function () { const clientIdTest1_ = '1aba6a40-f711-11e9-868c-53a2ae972xxx'; const sessionIdTest1_ = '1594782644-1aba6a40-f711-11e9-868c-53a2ae972xxx'; + const jxtokoTest1_ = 'eyJJRCI6ImFiYyJ9'; // to serve as the object that prebid will call jixie buildRequest with: (param2) const bidderRequest_ = { @@ -198,6 +199,9 @@ describe('jixie Adapter', function () { // get the interceptors ready: let getCookieStub = sinon.stub(storage, 'getCookie'); let getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getCookieStub + .withArgs('_jxtoko') + .returns(jxtokoTest1_); getCookieStub .withArgs('_jxx') .returns(clientIdTest1_); @@ -227,6 +231,7 @@ describe('jixie Adapter', function () { expect(payload).to.have.property('client_id_ls', clientIdTest1_); expect(payload).to.have.property('session_id_c', sessionIdTest1_); expect(payload).to.have.property('session_id_ls', sessionIdTest1_); + expect(payload).to.have.property('jxtoko_id', jxtokoTest1_); expect(payload).to.have.property('device', device_); expect(payload).to.have.property('domain', domain_); expect(payload).to.have.property('pageurl', pageurl_); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 77c8ce58442..5a38a971e09 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -659,9 +659,9 @@ describe('jwplayerRtdProvider', function() { const segment2 = 'segment2'; const segment3 = 'segment3'; const contentSegments = getContentSegments([segment1, segment2, segment3]); - expect(contentSegments[0]).to.deep.equal({ id: segment1, value: segment1 }); - expect(contentSegments[1]).to.deep.equal({ id: segment2, value: segment2 }); - expect(contentSegments[2]).to.deep.equal({ id: segment3, value: segment3 }); + expect(contentSegments[0]).to.deep.equal({ id: segment1 }); + expect(contentSegments[1]).to.deep.equal({ id: segment2 }); + expect(contentSegments[2]).to.deep.equal({ id: segment3 }); }); }); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 169ee520f33..525c00d655c 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -69,7 +69,33 @@ describe('kargo adapter tests', function () { userId: { tdid: 'fake-tdid' }, - sizes: [[320, 50], [300, 250], [300, 600]] + sizes: [[320, 50], [300, 250], [300, 600]], + ortb2: { + device: { + sua: { + platform: { + brand: 'macOS', + version: [ '12', '6', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + } + ], + mobile: 0, + model: '' + } + } + } }, { params: { @@ -259,6 +285,28 @@ describe('kargo adapter tests', function () { device: { width: screen.width, height: screen.height, + sua: { + platform: { + brand: 'macOS', + version: [ '12', '6', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + } + ], + mobile: 0, + model: '', + }, }, userIDs: { kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', @@ -280,13 +328,39 @@ describe('kargo adapter tests', function () { prebidRawBidRequests: [ { bidId: 1, + ortb2: { + device: { + sua: { + platform: { + brand: 'macOS', + version: [ '12', '6', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + } + ], + mobile: 0, + model: '' + } + } + }, params: { placementId: 'foo' }, userId: { tdid: 'fake-tdid' }, - sizes: [[320, 50], [300, 250], [300, 600]] + sizes: [[320, 50], [300, 250], [300, 600]], }, { bidId: 2, @@ -330,7 +404,6 @@ describe('kargo adapter tests', function () { var payload = { timeout: 200, uspConsent: '1---', - foo: 'bar', refererInfo: { page: 'https://www.prebid.org', }, @@ -349,7 +422,6 @@ describe('kargo adapter tests', function () { expect(request.method).to.equal('GET'); expect(request.currency).to.equal('USD'); expect(request.timeout).to.equal(200); - expect(request.foo).to.equal('bar'); expect(krakenParams).to.deep.equal(expected); // Make sure session ID stays the same across requests simulating multiple auctions on one page load for (let i in sessionIds) { @@ -484,6 +556,17 @@ describe('kargo adapter tests', function () { mediaType: 'video', metadata: {}, currency: 'EUR' + }, + 6: { + id: 'bar', + cpm: 2.5, + adm: '', + admUrl: 'https://foobar.com/vast_adm', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + currency: 'EUR' } }}, { currency: 'USD', @@ -512,6 +595,11 @@ describe('kargo adapter tests', function () { params: { placementId: 'bar' } + }, { + bidId: 6, + params: { + placementId: 'bar' + } }] }); var expectation = [{ @@ -592,6 +680,22 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } + }, { + requestId: '6', + cpm: 2.5, + width: 300, + height: 250, + ad: '', + vastUrl: 'https://foobar.com/vast_adm', + ttl: 300, + creativeId: 'bar', + dealId: undefined, + netRevenue: true, + currency: 'EUR', + mediaType: 'video', + meta: { + mediaType: 'video' + } }]; expect(resp).to.deep.equal(expectation); }); diff --git a/test/spec/modules/konduitAnalyticsAdapter_spec.js b/test/spec/modules/konduitAnalyticsAdapter_spec.js index 1612138cde4..e79ae2feeeb 100644 --- a/test/spec/modules/konduitAnalyticsAdapter_spec.js +++ b/test/spec/modules/konduitAnalyticsAdapter_spec.js @@ -121,7 +121,5 @@ describe(`Konduit Analytics Adapter`, () => { expect(requestBody.konduitId).to.be.equal(konduitId); expect(requestBody.prebidVersion).to.be.equal('$prebid.version$'); expect(requestBody.environment).to.be.an('object'); - // 6 Konduit events + 1 Clean.io event - sinon.assert.callCount(konduitAnalyticsAdapter.track, 7); }); }); diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js new file mode 100644 index 00000000000..7c0d8e92001 --- /dev/null +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -0,0 +1,560 @@ +import {expect} from 'chai'; +import { + spec as adapter, + SUPPORTED_ID_SYSTEMS, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from 'modules/kueezRtbBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER] +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + } + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['kueezrtb.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('KueezRtbBidAdapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + kueezrtb: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.kueezrtb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.kueezrtb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['kueezrtb.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + kueezrtb: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + kueezrtb: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const {value, created} = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 4efb4f4e84d..5a92110abb4 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -10,7 +10,12 @@ describe('limelightDigitalAdapter', function () { host: 'exchange.ortb.net', adUnitId: 123, adUnitType: 'banner', - publisherId: 'perfectPublisher' + publisherId: 'perfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -49,7 +54,12 @@ describe('limelightDigitalAdapter', function () { params: { host: 'ads.project-limelight.com', adUnitId: 456, - adUnitType: 'banner' + adUnitType: 'banner', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', @@ -90,7 +100,12 @@ describe('limelightDigitalAdapter', function () { host: 'exchange.ortb.net', adUnitId: 789, adUnitType: 'video', - publisherId: 'secondPerfectPublisher' + publisherId: 'secondPerfectPublisher', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', @@ -128,7 +143,12 @@ describe('limelightDigitalAdapter', function () { params: { host: 'exchange.ortb.net', adUnitId: 789, - adUnitType: 'video' + adUnitType: 'video', + custom1: 'custom1', + custom2: 'custom2', + custom3: 'custom3', + custom4: 'custom4', + custom5: 'custom5' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', @@ -196,7 +216,12 @@ describe('limelightDigitalAdapter', function () { 'transactionId', 'publisherId', 'userIdAsEids', - 'supplyChain' + 'supplyChain', + 'custom1', + 'custom2', + 'custom3', + 'custom4', + 'custom5' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -205,6 +230,11 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.sizes).to.be.an('array'); expect(adUnit.userIdAsEids).to.be.an('array'); expect(adUnit.supplyChain).to.be.an('object'); + expect(adUnit.custom1).to.be.a('string'); + expect(adUnit.custom2).to.be.a('string'); + expect(adUnit.custom3).to.be.a('string'); + expect(adUnit.custom4).to.be.a('string'); + expect(adUnit.custom5).to.be.a('string'); }) }) }) diff --git a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js index b3a452e5ece..fa4c5cd8cad 100644 --- a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js +++ b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js @@ -2,6 +2,7 @@ import liAnalytics from 'modules/liveIntentAnalyticsAdapter'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; import { auctionManager } from 'src/auctionManager.js'; +import {expectEvents} from '../../helpers/analytics.js'; let utils = require('src/utils'); let refererDetection = require('src/refererDetection'); @@ -286,12 +287,8 @@ describe('LiveIntent Analytics Adapter ', () => { }); it('track is called', () => { - liAnalytics.enableAnalytics(config); sandbox.stub(liAnalytics, 'track'); - events.emit(constants.EVENTS.AUCTION_END, args); - events.emit(constants.EVENTS.AUCTION_END, args); - events.emit(constants.EVENTS.AUCTION_END, args); - clock.tick(6000); - sinon.assert.callCount(liAnalytics.track, 3) + liAnalytics.enableAnalytics(config); + expectEvents().to.beTrackedBy(liAnalytics.track); }) }); diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index a4fee5e3b26..6bbdf6e1705 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -13,6 +13,10 @@ describe('Livewrapped adapter tests', function () { window.livewrapped = undefined; + config.setConfig({ + device: { w: 100, h: 100 } + }); + bidderRequest = { bidderCode: 'livewrapped', auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', @@ -43,6 +47,7 @@ describe('Livewrapped adapter tests', function () { afterEach(function () { sandbox.restore(); + config.resetConfig(); }); describe('isBidRequestValid', function() { @@ -351,7 +356,7 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); - it('should make a well-formed single request object with ad blocker revovered parameter', function() { + it('should make a well-formed single request object with ad blocker recovered parameter', function() { sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } }); sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); @@ -489,7 +494,7 @@ describe('Livewrapped adapter tests', function () { return {bundle: 'bundle', domain: 'https://appdomain.com'}; } if (key === 'device') { - return {ifa: 'ifa', width: 300, height: 200}; + return {ifa: 'ifa', w: 300, h: 200}; } return origGetConfig.apply(config, arguments); }); @@ -839,6 +844,266 @@ describe('Livewrapped adapter tests', function () { expect(data).to.deep.equal(expectedQuery); }); + + it('width and height should default to values from window when not set in config', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + config.resetConfig(); + + let testbidRequest = clone(bidderRequest); + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: window.innerWidth, + height: window.innerHeight, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + }); + + describe('price floors module', function() { + it('price floors module disabled', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor does not return an object', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return undefined; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns a NaN floor', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: undefined }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns unexpected currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'EUR' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}] + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns valid floor - ad server currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'currency.adServerCurrency') { + return 'EUR'; + } + return origGetConfig.apply(config, arguments); + }); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'EUR' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + flrCur: 'EUR', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + flr: 10 + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('getFloor returns valid floor - default currency', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let testbidRequest = clone(bidderRequest); + let bids = testbidRequest.bids.map(b => { + b.getFloor = function () { return { floor: 10, currency: 'USD' }; } + return b; + }); + let result = spec.buildRequests(bids, testbidRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + flrCur: 'USD', + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + flr: 10 + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); }); it('should make use of user ids if available', function() { diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 090144ab158..ea538db08e1 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -18,6 +18,7 @@ describe('LotameId', function() { let removeFromLocalStorageStub; let timeStampStub; let uspConsentDataStub; + let requestHost; const nowTimestamp = new Date().getTime(); @@ -33,6 +34,11 @@ describe('LotameId', function() { ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { + requestHost = 'https://c.ltmsphrcl.net/id'; + } else { + requestHost = 'https://id.crwdcntrl.net/id'; + } }); afterEach(function () { @@ -69,8 +75,7 @@ describe('LotameId', function() { }); it('should call the remote server when getId is called', function () { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); - + expect(request.url).to.be.eq(`${requestHost}`); expect(callBackSpy.calledOnce).to.be.true; }); @@ -440,7 +445,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -472,7 +477,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -504,7 +509,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true&gdpr_consent=consentGiven' + `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` ); }); }); @@ -532,7 +537,7 @@ describe('LotameId', function() { it('should not include the gdpr consent string on the url', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=true' + `${requestHost}?gdpr_applies=true` ); }); }); @@ -561,7 +566,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -590,7 +595,7 @@ describe('LotameId', function() { it('should pass the gdpr consent string back', function() { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_consent=consentGiven' + `${requestHost}?gdpr_consent=consentGiven` ); }); }); @@ -614,7 +619,7 @@ describe('LotameId', function() { }); it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq('https://id.crwdcntrl.net/id'); + expect(request.url).to.be.eq(`${requestHost}`); }); }); @@ -836,7 +841,7 @@ describe('LotameId', function() { it('should pass the usp consent string and client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&us_privacy=1NNN&c=1234' + `${requestHost}?gdpr_applies=false&us_privacy=1NNN&c=1234` ); }); @@ -924,7 +929,7 @@ describe('LotameId', function() { it('should pass client id back', function () { expect(request.url).to.be.eq( - 'https://id.crwdcntrl.net/id?gdpr_applies=false&c=1234' + `${requestHost}?gdpr_applies=false&c=1234` ); }); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..3606b4b4550 --- /dev/null +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -0,0 +1,2010 @@ +import magniteAdapter, { + parseBidResponse, + getHostNameFromReferer, + storage, + rubiConf, +} from '../../../modules/magniteAnalyticsAdapter.js'; +import CONSTANTS from 'src/constants.json'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; +import * as mockGpt from '../integration/faker/googletag.js'; +import { + setConfig, + addBidResponseHook, +} from 'modules/currency.js'; +import { getGlobal } from '../../../src/prebidGlobal.js'; +import { deepAccess } from '../../../src/utils.js'; + +let events = require('src/events.js'); +let utils = require('src/utils.js'); + +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + BILLABLE_EVENT + } +} = CONSTANTS; + +const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; + +// Mock Event Data +const MOCK = { + AUCTION_INIT: { + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'timestamp': 1658868383741, + 'adUnits': [ + { + 'code': 'box', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 267318, + 'zoneId': 1861698 + } + } + ], + 'sizes': [ + [ + 300, + 250 + ] + ], + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'ortb2Imp': { + 'ext': { + 'tid': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/1234567/prebid-slot' + }, + 'pbadslot': '/1234567/prebid-slot' + }, + 'gpid': '/1234567/prebid-slot' + } + } + } + ], + 'bidderRequests': [ + { + 'bidderCode': 'rubicon', + 'bids': [ + { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 267318, + 'zoneId': 1861698, + }, + 'adUnitCode': 'box', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'bidId': '23fcd8cf4bf0d7', + 'src': 'client', + 'startTime': 1658868383748 + } + ], + 'refererInfo': { + 'page': 'http://a-test-domain.com:8000/test_pages/sanity/TEMP/prebidTest.html?pbjs_debug=true', + }, + } + ], + 'timeout': 3000, + 'config': { + 'accountId': 1001, + 'endpoint': 'https://pba-event-service-alb-dev.use1.fanops.net/event' + } + }, + BID_REQUESTED: { + 'bidderCode': 'rubicon', + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'bids': [ + { + 'bidder': 'rubicon', + 'params': { + 'accountId': 1001, + 'siteId': 267318, + 'zoneId': 1861698, + }, + 'adUnitCode': 'box', + 'bidId': '23fcd8cf4bf0d7', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'src': 'client', + } + ] + }, + BID_RESPONSE: { + 'bidderCode': 'rubicon', + 'width': 300, + 'height': 250, + 'adId': '3c0b59947ced11', + 'requestId': '23fcd8cf4bf0d7', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'mediaType': 'banner', + 'source': 'client', + 'currency': 'USD', + 'creativeId': '4954828', + 'cpm': 3.4, + 'ttl': 300, + 'netRevenue': true, + 'ad': '', + 'bidder': 'rubicon', + 'adUnitCode': 'box', + 'timeToRespond': 271, + 'size': '300x250', + 'status': 'rendered', + getStatusCode: () => 1, + }, + AUCTION_END: { + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'auctionEnd': 1658868384019, + }, + BIDDER_DONE: { + 'bidderCode': 'rubicon', + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'bids': [ + { + 'bidder': 'rubicon', + 'adUnitCode': 'box', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'bidId': '23fcd8cf4bf0d7', + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'src': 'client', + } + ] + }, + BID_WON: { + 'bidderCode': 'rubicon', + 'bidId': '23fcd8cf4bf0d7', + 'adId': '3c0b59947ced11', + 'requestId': '23fcd8cf4bf0d7', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'mediaType': 'banner', + 'currency': 'USD', + 'cpm': 3.4, + 'ttl': 300, + 'bidder': 'rubicon', + 'adUnitCode': 'box', + 'status': 'rendered', + } +} + +const ANALYTICS_MESSAGE = { + 'channel': 'web', + 'integration': 'pbjs', + 'referrerUri': 'http://a-test-domain.com:8000/test_pages/sanity/TEMP/prebidTest.html?pbjs_debug=true', + 'version': '$prebid.version$', + 'referrerHostname': 'a-test-domain.com', + 'timestamps': { + 'timeSincePageLoad': 500, + 'eventTime': 1519767014281, + 'prebidLoaded': magniteAdapter.MODULE_INITIALIZED_TIME + }, + 'wrapper': { + 'name': '10000_fakewrapper_test' + }, + 'session': { + 'id': '12345678-1234-1234-1234-123456789abc', + 'pvid': '12345678', + 'start': 1519767013781, + 'expires': 1519788613781 + }, + 'auctions': [ + { + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'auctionStart': 1658868383741, + 'samplingFactor': 1, + 'clientTimeoutMillis': 3000, + 'accountId': 1001, + 'bidderOrder': [ + 'rubicon' + ], + 'serverTimeoutMillis': 1000, + 'adUnits': [ + { + 'adUnitCode': 'box', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'mediaTypes': [ + 'banner' + ], + 'dimensions': [ + { + 'width': 300, + 'height': 250 + } + ], + 'pbAdSlot': '/1234567/prebid-slot', + 'gpid': '/1234567/prebid-slot', + 'bids': [ + { + 'bidder': 'rubicon', + 'bidId': '23fcd8cf4bf0d7', + 'source': 'client', + 'status': 'success', + 'clientLatencyMillis': 271, + 'bidResponse': { + 'bidPriceUSD': 3.4, + 'mediaType': 'banner', + 'dimensions': { + 'width': 300, + 'height': 250 + } + } + } + ], + 'accountId': 1001, + 'siteId': 267318, + 'zoneId': 1861698, + 'status': 'success' + } + ], + 'auctionEnd': 1658868384019 + } + ], + 'gamRenders': [ + { + 'adSlot': 'box', + 'advertiserId': 1111, + 'creativeId': 2222, + 'lineItemId': 3333, + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a' + } + ], + 'bidsWon': [ + { + 'bidder': 'rubicon', + 'bidId': '23fcd8cf4bf0d7', + 'source': 'client', + 'status': 'success', + 'clientLatencyMillis': 271, + 'bidResponse': { + 'bidPriceUSD': 3.4, + 'mediaType': 'banner', + 'dimensions': { + 'width': 300, + 'height': 250 + } + }, + 'sourceAuctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'renderAuctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + 'sourceTransactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'renderTransactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'transactionId': '7b10a106-89ea-4e19-bc51-9b2e970fc42a', + 'accountId': 1001, + 'siteId': 267318, + 'zoneId': 1861698, + 'mediaTypes': [ + 'banner' + ], + 'adUnitCode': 'box' + } + ], + 'trigger': 'gam-delayed' +} + +describe('magnite analytics adapter', function () { + let sandbox; + let clock; + let getDataFromLocalStorageStub, setDataInLocalStorageStub, localStorageIsEnabledStub, removeDataFromLocalStorageStub; + let gptSlot0; + let gptSlotRenderEnded0; + beforeEach(function () { + mockGpt.enable(); + gptSlot0 = mockGpt.makeSlot({ code: 'box' }); + gptSlotRenderEnded0 = { + eventName: 'slotRenderEnded', + params: { + slot: gptSlot0, + isEmpty: false, + advertiserId: 1111, + sourceAgnosticCreativeId: 2222, + sourceAgnosticLineItemId: 3333 + } + }; + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + removeDataFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage') + sandbox = sinon.sandbox.create(); + + localStorageIsEnabledStub.returns(true); + + sandbox.stub(events, 'getEvents').returns([]); + + sandbox.stub(utils, 'generateUUID').returns(STUBBED_UUID); + + clock = sandbox.useFakeTimers(1519767013781); + + magniteAdapter.referrerHostname = ''; + + config.setConfig({ + s2sConfig: { + timeout: 1000, + accountId: 10000, + }, + rubicon: { + wrapperName: '10000_fakewrapper_test' + } + }) + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + mockGpt.enable(); + getDataFromLocalStorageStub.restore(); + setDataInLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + removeDataFromLocalStorageStub.restore(); + magniteAdapter.disableAnalytics(); + }); + + it('should require accountId', function () { + sandbox.stub(utils, 'logError'); + + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event' + } + }); + + expect(utils.logError.called).to.equal(true); + }); + + it('should require endpoint', function () { + sandbox.stub(utils, 'logError'); + + magniteAdapter.enableAnalytics({ + options: { + accountId: 1001 + } + }); + + expect(utils.logError.called).to.equal(true); + }); + + describe('config subscribe', function () { + it('should update the pvid if user asks', function () { + expect(utils.generateUUID.called).to.equal(false); + config.setConfig({ rubicon: { updatePageView: true } }); + expect(utils.generateUUID.called).to.equal(true); + }); + it('should merge in and preserve older set configs', function () { + config.setConfig({ + rubicon: { + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb' + }, + updatePageView: true + } + }); + expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 500, + analyticsBatchTimeout: 5000, + analyticsProcessDelay: 1, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + }, + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb' + }, + updatePageView: true + }); + + // update it with stuff + config.setConfig({ + rubicon: { + analyticsBatchTimeout: 3000, + fpkvs: { + link: 'email' + } + } + }); + expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 500, + analyticsBatchTimeout: 3000, + analyticsProcessDelay: 1, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + }, + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + source: 'fb', + link: 'email' + }, + updatePageView: true + }); + + // overwriting specific edge keys should update them + config.setConfig({ + rubicon: { + fpkvs: { + link: 'iMessage', + source: 'twitter' + } + } + }); + expect(rubiConf).to.deep.equal({ + analyticsEventDelay: 500, + analyticsBatchTimeout: 3000, + analyticsProcessDelay: 1, + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + }, + pvid: '12345678', + wrapperName: '1001_general', + int_type: 'dmpbjs', + fpkvs: { + link: 'iMessage', + source: 'twitter' + }, + updatePageView: true + }); + }); + }); + + describe('when handling events', function () { + function performStandardAuction({ + gptEvents = [gptSlotRenderEnded0], + auctionId = MOCK.AUCTION_INIT.auctionId, + eventDelay = rubiConf.analyticsEventDelay, + sendBidWon = true + } = {}) { + events.emit(AUCTION_INIT, { ...MOCK.AUCTION_INIT, auctionId }); + events.emit(BID_REQUESTED, { ...MOCK.BID_REQUESTED, auctionId }); + events.emit(BID_RESPONSE, { ...MOCK.BID_RESPONSE, auctionId }); + events.emit(BIDDER_DONE, { ...MOCK.BIDDER_DONE, auctionId }); + events.emit(AUCTION_END, { ...MOCK.AUCTION_END, auctionId }); + + if (gptEvents && gptEvents.length) { + gptEvents.forEach(gptEvent => mockGpt.emitEvent(gptEvent.eventName, gptEvent.params)); + } + + if (sendBidWon) { + events.emit(BID_WON, { ...MOCK.BID_WON, auctionId }); + } + + if (eventDelay > 0) { + clock.tick(eventDelay); + } + } + + beforeEach(function () { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + config.setConfig({ rubicon: { updatePageView: true } }); + }); + + it('should build a batched message from prebid events', function () { + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + expect(request.url).to.equal('//localhost:9999/event'); + + let message = JSON.parse(request.requestBody); + + expect(message).to.deep.equal(ANALYTICS_MESSAGE); + }); + + it('should pass along bidderOrder correctly', function () { + const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + + auctionInit.bidderRequests = auctionInit.bidderRequests.concat([ + { bidderCode: 'pubmatic' }, + { bidderCode: 'ix' }, + { bidderCode: 'appnexus' } + ]) + + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].bidderOrder).to.deep.equal([ + 'rubicon', + 'pubmatic', + 'ix', + 'appnexus' + ]); + }); + + it('should pass along 1x1 size if no sizes in adUnit', function () { + const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + + delete auctionInit.adUnits[0].sizes; + + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].adUnits[0].dimensions).to.deep.equal([ + { + width: 1, + height: 1 + } + ]); + }); + + it('should pass along user ids', function () { + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.bidderRequests[0].bids[0].userId = { + criteoId: 'sadfe4334', + lotamePanoramaId: 'asdf3gf4eg', + pubcid: 'dsfa4545-svgdfs5', + sharedId: { id1: 'asdf', id2: 'sadf4344' } + }; + + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + + expect(message.auctions[0].user).to.deep.equal({ + ids: [ + { provider: 'criteoId', 'hasId': true }, + { provider: 'lotamePanoramaId', 'hasId': true }, + { provider: 'pubcid', 'hasId': true }, + { provider: 'sharedId', 'hasId': true }, + ] + }); + }); + + // A-Domain tests + [ + { input: ['magnite.com'], expected: ['magnite.com'] }, + { input: ['magnite.com', 'prebid.org'], expected: ['magnite.com', 'prebid.org'] }, + { input: [123, 'prebid.org', false, true, [], 'magnite.com', {}], expected: ['prebid.org', 'magnite.com'] }, + { input: 'not array', expected: undefined }, + { input: [], expected: undefined }, + ].forEach((test, index) => { + it(`should handle adomain correctly - #${index + 1}`, function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + bidResponse.meta = { + advertiserDomains: test.input + } + + events.emit(BID_RESPONSE, bidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(BID_WON, MOCK.BID_WON); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(test.expected); + }); + + // Network Id tests + [ + { input: 'magnite.com', expected: 'magnite.com' }, + { input: 12345, expected: '12345' }, + { input: ['magnite.com', 12345], expected: 'magnite.com,12345' } + ].forEach((test, index) => { + it(`should handle networkId correctly - #${index + 1}`, function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + bidResponse.meta = { + networkId: test.input + }; + + events.emit(BID_RESPONSE, bidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(BID_WON, MOCK.BID_WON); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.networkId).to.equal(test.expected); + }); + }); + }); + + describe('with session handling', function () { + const expectedPvid = STUBBED_UUID.slice(0, 8); + beforeEach(function () { + config.setConfig({ rubicon: { updatePageView: true } }); + }); + + it('should not log any session data if local storage is not enabled', function () { + localStorageIsEnabledStub.returns(false); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + delete expectedMessage.session; + delete expectedMessage.fpkvs; + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + expect(request.url).to.equal('//localhost:9999/event'); + + let message = JSON.parse(request.requestBody); + + expect(message).to.deep.equal(expectedMessage); + }); + + it('should should pass along custom rubicon kv and pvid when defined', function () { + config.setConfig({ + rubicon: { + fpkvs: { + source: 'fb', + link: 'email' + } + } + }); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); + expectedMessage.fpkvs = [ + { key: 'source', value: 'fb' }, + { key: 'link', value: 'email' } + ] + expect(message).to.deep.equal(expectedMessage); + }); + + it('should convert kvs to strings before sending', function () { + config.setConfig({ + rubicon: { + fpkvs: { + number: 24, + boolean: false, + string: 'hello', + array: ['one', 2, 'three'], + object: { one: 'two' } + } + } + }); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); + expectedMessage.fpkvs = [ + { key: 'number', value: '24' }, + { key: 'boolean', value: 'false' }, + { key: 'string', value: 'hello' }, + { key: 'array', value: 'one,2,three' }, + { key: 'object', value: '[object Object]' } + ] + expect(message).to.deep.equal(expectedMessage); + }); + + it('should use the query utm param rubicon kv value and pass updated kv and pvid when defined', function () { + sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=other', 'pbjs_debug': 'true' }); + + config.setConfig({ + rubicon: { + fpkvs: { + source: 'fb', + link: 'email' + } + } + }); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session.pvid = STUBBED_UUID.slice(0, 8); + expectedMessage.fpkvs = [ + { key: 'source', value: 'other' }, + { key: 'link', value: 'email' } + ] + + message.fpkvs.sort((left, right) => left.key < right.key); + expectedMessage.fpkvs.sort((left, right) => left.key < right.key); + + expect(message).to.deep.equal(expectedMessage); + }); + + it('should pick up existing localStorage and use its values', function () { + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519767017881, // 15 mins before "now" + expires: 1519767039481, // six hours later + lastSeen: 1519766113781, + fpkvs: { source: 'tw' } + }; + getDataFromLocalStorageStub.withArgs('mgniSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } + } + }); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session = { + id: '987654', + start: 1519767017881, + expires: 1519767039481, + pvid: expectedPvid + } + expectedMessage.fpkvs = [ + { key: 'source', value: 'tw' }, + { key: 'link', value: 'email' } + ] + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: '987654', // should have stayed same + start: 1519767017881, // should have stayed same + expires: 1519767039481, // should have stayed same + lastSeen: 1519767013781, // lastSeen updated to our auction init time + fpkvs: { source: 'tw', link: 'email' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + + it('should overwrite matching localstorge value and use its remaining values', function () { + sandbox.stub(utils, 'getWindowLocation').returns({ 'search': '?utm_source=fb&utm_click=dog' }); + + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519766113781, // 15 mins before "now" + expires: 1519787713781, // six hours later + lastSeen: 1519766113781, + fpkvs: { source: 'tw', link: 'email' } + }; + getDataFromLocalStorageStub.withArgs('mgniSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } + } + }); + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.session = { + id: '987654', + start: 1519766113781, + expires: 1519787713781, + pvid: expectedPvid + } + expectedMessage.fpkvs = [ + { key: 'source', value: 'fb' }, + { key: 'link', value: 'email' }, + { key: 'click', value: 'dog' } + ] + + message.fpkvs.sort((left, right) => left.key < right.key); + expectedMessage.fpkvs.sort((left, right) => left.key < right.key); + + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: '987654', // should have stayed same + start: 1519766113781, // should have stayed same + expires: 1519787713781, // should have stayed same + lastSeen: 1519767013781, // lastSeen updated to our auction init time + fpkvs: { source: 'fb', link: 'email', click: 'dog' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + + it('should throw out session if lastSeen > 30 mins ago and create new one', function () { + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519764313781, // 45 mins before "now" + expires: 1519785913781, // six hours later + lastSeen: 1519764313781, // 45 mins before "now" + fpkvs: { source: 'tw' } + }; + getDataFromLocalStorageStub.withArgs('mgniSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } + } + }); + + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid + expectedMessage.session.pvid = expectedPvid; + + // the saved fpkvs should have been thrown out since session expired + expectedMessage.fpkvs = [ + { key: 'link', value: 'email' } + ] + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: STUBBED_UUID, // should have generated not used input + start: 1519767013781, // updated to whenever auction init started + expires: 1519788613781, // 6 hours after start + lastSeen: 1519767013781, // lastSeen updated to our "now" + fpkvs: { link: 'email' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + + it('should throw out session if past expires time and create new one', function () { + // set some localStorage + let inputlocalStorage = { + id: '987654', + start: 1519745353781, // 6 hours before "expires" + expires: 1519766953781, // little more than six hours ago + lastSeen: 1519767008781, // 5 seconds ago + fpkvs: { source: 'tw' } + }; + getDataFromLocalStorageStub.withArgs('mgniSession').returns(btoa(JSON.stringify(inputlocalStorage))); + + config.setConfig({ + rubicon: { + fpkvs: { + link: 'email' // should merge this with what is in the localStorage! + } + } + }); + + performStandardAuction(); + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + // session should match what is already in ANALYTICS_MESSAGE, just need to add pvid + expectedMessage.session.pvid = expectedPvid; + + // the saved fpkvs should have been thrown out since session expired + expectedMessage.fpkvs = [ + { key: 'link', value: 'email' } + ] + expect(message).to.deep.equal(expectedMessage); + + let calledWith; + try { + calledWith = JSON.parse(atob(setDataInLocalStorageStub.getCall(0).args[1])); + } catch (e) { + calledWith = {}; + } + + expect(calledWith).to.deep.equal({ + id: STUBBED_UUID, // should have generated and not used same one + start: 1519767013781, // updated to whenever auction init started + expires: 1519788613781, // 6 hours after start + lastSeen: 1519767013781, // lastSeen updated to our "now" + fpkvs: { link: 'email' }, // link merged in + pvid: expectedPvid // new pvid stored + }); + }); + }); + + it('should send gam data if adunit has elementid ortb2 fields', function () { + // update auction init mock to have the elementids in the adunit + // and change adUnitCode to be hashes + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + auctionInit.adUnits[0].ortb2Imp.ext.data.elementid = [gptSlot0.getSlotElementId()]; + auctionInit.adUnits[0].code = '1a2b3c4d'; + + // bid request + let bidRequested = utils.deepClone(MOCK.BID_REQUESTED); + bidRequested.bids[0].adUnitCode = '1a2b3c4d'; + + // bid response + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + bidResponse.adUnitCode = '1a2b3c4d'; + + // bidder done + let bidderDone = utils.deepClone(MOCK.BIDDER_DONE); + bidderDone.bids[0].adUnitCode = '1a2b3c4d'; + + // bidder done + let bidWon = utils.deepClone(MOCK.BID_WON); + bidWon.adUnitCode = '1a2b3c4d'; + + // Run auction + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, bidRequested); + events.emit(BID_RESPONSE, bidResponse); + events.emit(BIDDER_DONE, bidderDone); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + events.emit(BID_WON, bidWon); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + + // new adUnitCodes in payload + expectedMessage.auctions[0].adUnits[0].adUnitCode = '1a2b3c4d'; + expectedMessage.bidsWon[0].adUnitCode = '1a2b3c4d'; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should delay the event call depending on analyticsEventDelay config', function () { + config.setConfig({ + rubicon: { + analyticsEventDelay: 2000 + } + }); + performStandardAuction({ eventDelay: 0 }); + + // Should not be sent until delay + expect(server.requests.length).to.equal(0); + + // tick the clock and it should fire + clock.tick(2000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + // The timestamps should be changed from the default by (set eventDelay (2000) - eventDelay default (500)) + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + expectedMessage.timestamps.eventTime = expectedMessage.timestamps.eventTime + 1500; + expectedMessage.timestamps.timeSincePageLoad = expectedMessage.timestamps.timeSincePageLoad + 1500; + + expect(message).to.deep.equal(expectedMessage); + }); + + ['seatBidId', 'pbsBidId'].forEach(pbsParam => { + it(`should overwrite prebid bidId with incoming PBS ${pbsParam}`, function () { + // bid response + let seatBidResponse = utils.deepClone(MOCK.BID_RESPONSE); + seatBidResponse[pbsParam] = 'abc-123-do-re-me'; + + // Run auction + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + + // new adUnitCodes in payload + expectedMessage.auctions[0].adUnits[0].bids[0].bidId = 'abc-123-do-re-me'; + expectedMessage.auctions[0].adUnits[0].bids[0].oldBidId = '23fcd8cf4bf0d7'; + expectedMessage.bidsWon[0].bidId = 'abc-123-do-re-me'; + expect(message).to.deep.equal(expectedMessage); + }); + }); + + [0, '0'].forEach(pbsParam => { + it(`should generate new bidId if incoming pbsBidId is ${pbsParam}`, function () { + // bid response + let seatBidResponse = utils.deepClone(MOCK.BID_RESPONSE); + seatBidResponse.pbsBidId = pbsParam; + + // Run auction + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + + // new adUnitCodes in payload + expectedMessage.auctions[0].adUnits[0].bids[0].bidId = STUBBED_UUID; + expectedMessage.auctions[0].adUnits[0].bids[0].oldBidId = '23fcd8cf4bf0d7'; + expectedMessage.bidsWon[0].bidId = STUBBED_UUID; + expect(message).to.deep.equal(expectedMessage); + }); + }); + + it(`should pick highest cpm if more than one bidResponse comes in`, function () { + // Run auction + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + const bidResp = utils.deepClone(MOCK.BID_RESPONSE); + + // emit some bid responses + [1.0, 5.5, 0.1].forEach(cpm => { + events.emit(BID_RESPONSE, { ...bidResp, cpm }); + }); + + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + + // highest cpm in payload + expectedMessage.auctions[0].adUnits[0].bids[0].bidResponse.bidPriceUSD = 5.5; + expectedMessage.bidsWon[0].bidResponse.bidPriceUSD = 5.5; + expect(message).to.deep.equal(expectedMessage); + }); + + it('should send bid won events by themselves if emitted after auction pba payload is sent', function () { + performStandardAuction({ sendBidWon: false }); + + // Now send bidWon + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + // should see two server requests + expect(server.requests.length).to.equal(2); + + // first is normal analytics event without bidWon + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + delete expectedMessage.bidsWon; + + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.deep.equal(expectedMessage); + + // second is just a bidWon (remove gam and auction event) + message = JSON.parse(server.requests[1].requestBody); + + let expectedMessage2 = utils.deepClone(ANALYTICS_MESSAGE); + delete expectedMessage2.auctions; + delete expectedMessage2.gamRenders; + + // second event should be event delay time after first one + expectedMessage2.timestamps.eventTime = expectedMessage.timestamps.eventTime + rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay; + expectedMessage2.timestamps.timeSincePageLoad = expectedMessage.timestamps.timeSincePageLoad + rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay; + + // trigger is `batched-bidsWon` + expectedMessage2.trigger = 'batched-bidsWon'; + + expect(message).to.deep.equal(expectedMessage2); + }); + + it('should send gamRender events by themselves if emitted after auction pba payload is sent', function () { + // dont send extra events and hit the batch timeout + performStandardAuction({ gptEvents: [], sendBidWon: false, eventDelay: rubiConf.analyticsBatchTimeout }); + + // Now send gptEvent and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + // should see two server requests + expect(server.requests.length).to.equal(2); + + // first is normal analytics event without bidWon or gam + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + delete expectedMessage.bidsWon; + delete expectedMessage.gamRenders; + + // timing changes a bit -> timestamps should be batchTimeout - event delay later + const expectedExtraTime = rubiConf.analyticsBatchTimeout - rubiConf.analyticsEventDelay; + expectedMessage.timestamps.eventTime = expectedMessage.timestamps.eventTime + expectedExtraTime; + expectedMessage.timestamps.timeSincePageLoad = expectedMessage.timestamps.timeSincePageLoad + expectedExtraTime; + + // since gam event did not fire, the trigger should be auctionEnd + expectedMessage.trigger = 'auctionEnd'; + + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.deep.equal(expectedMessage); + + // second is gam and bid won + message = JSON.parse(server.requests[1].requestBody); + + let expectedMessage2 = utils.deepClone(ANALYTICS_MESSAGE); + // second event should be event delay time after first one + expectedMessage2.timestamps.eventTime = expectedMessage.timestamps.eventTime + rubiConf.analyticsEventDelay; + expectedMessage2.timestamps.timeSincePageLoad = expectedMessage.timestamps.timeSincePageLoad + rubiConf.analyticsEventDelay; + delete expectedMessage2.auctions; + + // trigger should be `batched-bidsWon-gamRender` + expectedMessage2.trigger = 'batched-bidsWon-gamRenders'; + + expect(message).to.deep.equal(expectedMessage2); + }); + + it('should send all events solo if delay and batch set to 0', function () { + const defaultDelay = rubiConf.analyticsEventDelay; + config.setConfig({ + rubicon: { + analyticsBatchTimeout: 0, + analyticsEventDelay: 0, + analyticsProcessDelay: 0 + } + }); + + performStandardAuction({ eventDelay: 0 }); + + // should be 3 requests + expect(server.requests.length).to.equal(3); + + // grab expected 3 requests from default message + let { auctions, gamRenders, bidsWon, ...rest } = utils.deepClone(ANALYTICS_MESSAGE); + + // rest of payload should have timestamps changed to be - default eventDelay since we changed it to 0 + rest.timestamps.eventTime = rest.timestamps.eventTime - defaultDelay; + rest.timestamps.timeSincePageLoad = rest.timestamps.timeSincePageLoad - defaultDelay; + + // loop through and assert events fired in correct order with correct stuff + [ + { expectedMessage: { auctions, ...rest }, trigger: 'solo-auction' }, + { expectedMessage: { gamRenders, ...rest }, trigger: 'solo-gam' }, + { expectedMessage: { bidsWon, ...rest }, trigger: 'solo-bidWon' }, + ].forEach((stuff, requestNum) => { + let message = JSON.parse(server.requests[requestNum].requestBody); + stuff.expectedMessage.trigger = stuff.trigger; + expect(message).to.deep.equal(stuff.expectedMessage); + }); + }); + + it(`should correctly mark bids as timed out`, function () { + // Run auction (simulate bidder timed out in 1000 ms) + const auctionStart = Date.now() - 1000; + events.emit(AUCTION_INIT, { ...MOCK.AUCTION_INIT, timestamp: auctionStart }); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // emit bid timeout + events.emit(BID_TIMEOUT, [ + { + auctionId: MOCK.AUCTION_INIT.auctionId, + adUnitCode: MOCK.AUCTION_INIT.adUnits[0].code, + bidId: MOCK.BID_REQUESTED.bids[0].bidId, + transactionId: MOCK.AUCTION_INIT.adUnits[0].transactionId, + } + ]); + + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + + // should see error time out bid + expectedMessage.auctions[0].adUnits[0].bids[0].status = 'error'; + expectedMessage.auctions[0].adUnits[0].bids[0].error = { + code: 'timeout-error', + description: 'prebid.js timeout' // will help us diff if timeout was set by PBS or PBJS + }; + + // should not see bidResponse or bidsWon + delete expectedMessage.auctions[0].adUnits[0].bids[0].bidResponse; + delete expectedMessage.bidsWon; + + // adunit should be marked as error + expectedMessage.auctions[0].adUnits[0].status = 'error'; + + // timed out in 1000 ms + expectedMessage.auctions[0].adUnits[0].bids[0].clientLatencyMillis = 1000; + + expectedMessage.auctions[0].auctionStart = auctionStart; + + expect(message).to.deep.equal(expectedMessage); + }); + + [ + { name: 'aupname', adUnitPath: 'adUnits.0.ortb2Imp.ext.data.aupname', eventPath: 'auctions.0.adUnits.0.pattern', input: '1234/mycoolsite/*&gpt_leaderboard&deviceType=mobile' }, + { name: 'gpid', adUnitPath: 'adUnits.0.ortb2Imp.ext.gpid', eventPath: 'auctions.0.adUnits.0.gpid', input: '1234/gpid/path' }, + { name: 'pbadslot', adUnitPath: 'adUnits.0.ortb2Imp.ext.data.pbadslot', eventPath: 'auctions.0.adUnits.0.pbAdSlot', input: '1234/pbadslot/path' } + ].forEach(test => { + it(`should correctly pass ${test.name}`, function () { + // bid response + let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); + utils.deepSetValue(auctionInit, test.adUnitPath, test.input); + + // Run auction + events.emit(AUCTION_INIT, auctionInit); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + // pattern in payload + expect(deepAccess(message, test.eventPath)).to.equal(test.input); + }); + }); + + it('should pass bidderDetail for multibid auctions', function () { + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + bidResponse.targetingBidder = 'rubi2'; + bidResponse.originalRequestId = bidResponse.requestId; + bidResponse.requestId = '1a2b3c4d5e6f7g8h9'; + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, bidResponse); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + let bidWon = utils.deepClone(MOCK.BID_WON); + bidWon.bidId = bidWon.requestId = '1a2b3c4d5e6f7g8h9'; + bidWon.bidderDetail = 'rubi2'; + events.emit(BID_WON, bidWon); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + + let message = JSON.parse(server.requests[0].requestBody); + + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + + // expect an extra bid added + expectedMessage.auctions[0].adUnits[0].bids.push({ + ...ANALYTICS_MESSAGE.auctions[0].adUnits[0].bids[0], + bidderDetail: 'rubi2', + bidId: '1a2b3c4d5e6f7g8h9' + }); + + // bid won is our extra bid + expectedMessage.bidsWon[0].bidderDetail = 'rubi2'; + expectedMessage.bidsWon[0].bidId = '1a2b3c4d5e6f7g8h9'; + + expect(message).to.deep.equal(expectedMessage); + }); + + it('should pass bidderDetail for multibid auctions', function () { + // Set the rates + setConfig({ + adServerCurrency: 'JPY', + rates: { + USD: { + JPY: 100 + } + } + }); + + // set our bid response to JPY + let bidResponse = utils.deepClone(MOCK.BID_RESPONSE); + bidResponse.currency = 'JPY'; + bidResponse.cpm = 100; + + // Now add the bidResponse hook which hooks on the currenct conversion function onto the bid response + let innerBid; + addBidResponseHook(function (adCodeId, bid) { + innerBid = bid; + }, 'elementId', bidResponse); + + // Use the rubi analytics parseBidResponse Function to get the resulting cpm from the bid response! + const bidResponseObj = parseBidResponse(innerBid); + expect(bidResponseObj).to.have.property('bidPriceUSD'); + expect(bidResponseObj.bidPriceUSD).to.equal(1.0); + }); + + it('should use the integration type provided in the config instead of the default', () => { + config.setConfig({ + rubicon: { + int_type: 'testType' + } + }) + + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.integration).to.equal('testType'); + }); + + it('should correctly pass bid.source when is s2s', () => { + // Run auction + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + + const bidReq = utils.deepClone(MOCK.BID_REQUESTED); + bidReq.bids[0].src = 's2s'; + + events.emit(BID_REQUESTED, bidReq); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + let expectedMessage = utils.deepClone(ANALYTICS_MESSAGE); + + // bid source should be 'server' + expectedMessage.auctions[0].adUnits[0].bids[0].source = 'server'; + expectedMessage.bidsWon[0].source = 'server'; + expect(message).to.deep.equal(expectedMessage); + }); + + describe('when handling bid caching', () => { + let auctionInits, bidRequests, bidResponses, bidsWon; + beforeEach(function () { + // set timing stuff to 0 so we clearly know when things fire + config.setConfig({ + useBidCache: true, + rubicon: { + analyticsEventDelay: 0, + analyticsBatchTimeout: 0, + analyticsProcessDelay: 0 + } + }); + + // setup 3 auctions + auctionInits = [ + { ...MOCK.AUCTION_INIT, auctionId: 'auctionId-1', adUnits: [{ ...MOCK.AUCTION_INIT.adUnits[0], transactionId: 'tid-1' }] }, + { ...MOCK.AUCTION_INIT, auctionId: 'auctionId-2', adUnits: [{ ...MOCK.AUCTION_INIT.adUnits[0], transactionId: 'tid-2' }] }, + { ...MOCK.AUCTION_INIT, auctionId: 'auctionId-3', adUnits: [{ ...MOCK.AUCTION_INIT.adUnits[0], transactionId: 'tid-3' }] } + ]; + bidRequests = [ + { ...MOCK.BID_REQUESTED, auctionId: 'auctionId-1', bids: [{ ...MOCK.BID_REQUESTED.bids[0], bidId: 'bidId-1', transactionId: 'tid-1' }] }, + { ...MOCK.BID_REQUESTED, auctionId: 'auctionId-2', bids: [{ ...MOCK.BID_REQUESTED.bids[0], bidId: 'bidId-2', transactionId: 'tid-2' }] }, + { ...MOCK.BID_REQUESTED, auctionId: 'auctionId-3', bids: [{ ...MOCK.BID_REQUESTED.bids[0], bidId: 'bidId-3', transactionId: 'tid-3' }] } + ]; + bidResponses = [ + { ...MOCK.BID_RESPONSE, auctionId: 'auctionId-1', transactionId: 'tid-1', requestId: 'bidId-1' }, + { ...MOCK.BID_RESPONSE, auctionId: 'auctionId-2', transactionId: 'tid-2', requestId: 'bidId-2' }, + { ...MOCK.BID_RESPONSE, auctionId: 'auctionId-3', transactionId: 'tid-3', requestId: 'bidId-3' }, + ]; + bidsWon = [ + { ...MOCK.BID_WON, auctionId: 'auctionId-1', transactionId: 'tid-1', bidId: 'bidId-1', requestId: 'bidId-1' }, + { ...MOCK.BID_WON, auctionId: 'auctionId-2', transactionId: 'tid-2', bidId: 'bidId-2', requestId: 'bidId-2' }, + { ...MOCK.BID_WON, auctionId: 'auctionId-3', transactionId: 'tid-3', bidId: 'bidId-3', requestId: 'bidId-3' }, + ]; + }); + function runBasicAuction(auctionNum) { + events.emit(AUCTION_INIT, auctionInits[auctionNum]); + events.emit(BID_REQUESTED, bidRequests[auctionNum]); + events.emit(BID_RESPONSE, bidResponses[auctionNum]); + events.emit(BIDDER_DONE, { ...MOCK.BIDDER_DONE, auctionId: auctionInits[auctionNum].auctionId }); + events.emit(AUCTION_END, { ...MOCK.AUCTION_END, auctionId: auctionInits[auctionNum].auctionId }); + } + it('should select earliest auction to attach to', () => { + // get 3 auctions pending to send events + runBasicAuction(0); + runBasicAuction(1); + runBasicAuction(2); + + // emmit a gptEvent should attach to first auction + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + // should be 4 requests so far (3 auctions + 1 gamRender) + expect(server.requests.length).to.equal(4); + + // 4th should be gamRender and should have Auciton # 1's id's + const message = JSON.parse(server.requests[3].requestBody); + const expectedMessage = { + ...ANALYTICS_MESSAGE.gamRenders[0], + auctionId: 'auctionId-1', + transactionId: 'tid-1' + }; + expect(message.gamRenders).to.deep.equal([expectedMessage]); + + // emit bidWon from first auction + events.emit(BID_WON, bidsWon[0]); + + // another request which is bidWon + expect(server.requests.length).to.equal(5); + const message1 = JSON.parse(server.requests[4].requestBody); + const expectedMessage1 = { + ...ANALYTICS_MESSAGE.bidsWon[0], + sourceAuctionId: 'auctionId-1', + renderAuctionId: 'auctionId-1', + sourceTransactionId: 'tid-1', + renderTransactionId: 'tid-1', + transactionId: 'tid-1', + bidId: 'bidId-1', + }; + expect(message1.bidsWon).to.deep.equal([expectedMessage1]); + }); + + [ + { useBidCache: true, expectedRenderId: 3 }, + { useBidCache: false, expectedRenderId: 2 } + ].forEach(test => { + it(`should match bidWon to correct render auction if useBidCache is ${test.useBidCache}`, () => { + config.setConfig({ useBidCache: test.useBidCache }); + // get 3 auctions pending to send events + runBasicAuction(0); + runBasicAuction(1); + runBasicAuction(2); + + // emmit 3 gpt Events, first two "empty" + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, { + slot: gptSlot0, + isEmpty: true, + }); + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, { + slot: gptSlot0, + isEmpty: true, + }); + // last one is valid + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + // should be 6 requests so far (3 auctions + 3 gamRender) + expect(server.requests.length).to.equal(6); + + // 4th should be gamRender and should have Auciton # 1's id's + const message = JSON.parse(server.requests[3].requestBody); + const expectedMessage = { + auctionId: 'auctionId-1', + transactionId: 'tid-1', + isSlotEmpty: true, + adSlot: 'box' + }; + expect(message.gamRenders).to.deep.equal([expectedMessage]); + + // 5th should be gamRender and should have Auciton # 2's id's + const message1 = JSON.parse(server.requests[4].requestBody); + const expectedMessage1 = { + auctionId: 'auctionId-2', + transactionId: 'tid-2', + isSlotEmpty: true, + adSlot: 'box' + }; + expect(message1.gamRenders).to.deep.equal([expectedMessage1]); + + // 6th should be gamRender and should have Auciton # 3's id's + const message2 = JSON.parse(server.requests[5].requestBody); + const expectedMessage2 = { + ...ANALYTICS_MESSAGE.gamRenders[0], + auctionId: 'auctionId-3', + transactionId: 'tid-3' + }; + expect(message2.gamRenders).to.deep.equal([expectedMessage2]); + + // emit bidWon from second auction + // it should pick out render information from 3rd auction and source from 1st + events.emit(BID_WON, bidsWon[1]); + + // another request which is bidWon + expect(server.requests.length).to.equal(7); + const message3 = JSON.parse(server.requests[6].requestBody); + const expectedMessage3 = { + ...ANALYTICS_MESSAGE.bidsWon[0], + sourceAuctionId: 'auctionId-2', + renderAuctionId: `auctionId-${test.expectedRenderId}`, + sourceTransactionId: 'tid-2', + renderTransactionId: `tid-${test.expectedRenderId}`, + transactionId: 'tid-2', + bidId: 'bidId-2' + }; + if (test.useBidCache) expectedMessage3.isCachedBid = true + expect(message3.bidsWon).to.deep.equal([expectedMessage3]); + }); + }); + + it('should still fire bidWon if no gam match found', () => { + // get 3 auctions pending to send events + runBasicAuction(0); + runBasicAuction(1); + runBasicAuction(2); + + // emit bidWon from 3rd auction - it should still fire even though no associated gamRender found + events.emit(BID_WON, bidsWon[2]); + + // another request which is bidWon + expect(server.requests.length).to.equal(4); + const message1 = JSON.parse(server.requests[3].requestBody); + const expectedMessage1 = { + ...ANALYTICS_MESSAGE.bidsWon[0], + sourceAuctionId: 'auctionId-3', + renderAuctionId: 'auctionId-3', + sourceTransactionId: 'tid-3', + renderTransactionId: 'tid-3', + transactionId: 'tid-3', + bidId: 'bidId-3', + }; + expect(message1.bidsWon).to.deep.equal([expectedMessage1]); + }); + }); + }); + + describe('billing events integration', () => { + beforeEach(function () { + magniteAdapter.enableAnalytics({ + options: { + endpoint: '//localhost:9999/event', + accountId: 1001 + } + }); + // default dmBilling + config.setConfig({ + rubicon: { + dmBilling: { + enabled: false, + vendors: [], + waitForAuction: true + } + } + }) + }); + afterEach(function () { + magniteAdapter.disableAnalytics(); + }); + const basicBillingAuction = (billingEvents = []) => { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + billingEvents.forEach(ev => events.emit(BILLABLE_EVENT, ev)); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + } + it('should ignore billing events when not enabled', () => { + basicBillingAuction([{ + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + }]); + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.billableEvents).to.be.undefined; + }); + it('should ignore billing events when enabled but vendor is not whitelisted', () => { + // off by default + config.setConfig({ + rubicon: { + dmBilling: { + enabled: true + } + } + }); + basicBillingAuction([{ + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + }]); + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.billableEvents).to.be.undefined; + }); + it('should ignore billing events if billingId is not defined or billingId is not a string', () => { + // off by default + config.setConfig({ + rubicon: { + dmBilling: { + enabled: true, + vendors: ['vendorName'] + } + } + }); + basicBillingAuction([ + { + vendor: 'vendorName', + type: 'auction', + }, + { + vendor: 'vendorName', + type: 'auction', + billingId: true + }, + { + vendor: 'vendorName', + type: 'auction', + billingId: 1233434 + }, + { + vendor: 'vendorName', + type: 'auction', + billingId: null + } + ]); + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message.billableEvents).to.be.undefined; + }); + it('should pass along billing event in same payload if same auctionId', () => { + // off by default + config.setConfig({ + rubicon: { + dmBilling: { + enabled: true, + vendors: ['vendorName'] + } + } + }); + basicBillingAuction([{ + vendor: 'vendorName', + type: 'pageView', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965', + auctionId: MOCK.AUCTION_INIT.auctionId + }]); + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message).to.haveOwnProperty('auctions'); + expect(message.billableEvents).to.deep.equal([{ + accountId: 1001, + vendor: 'vendorName', + type: 'pageView', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965', + auctionId: MOCK.AUCTION_INIT.auctionId + }]); + }); + it('should pass NOT pass along billing event in same payload if no auctionId', () => { + // off by default + config.setConfig({ + rubicon: { + dmBilling: { + enabled: true, + vendors: ['vendorName'] + } + } + }); + basicBillingAuction([{ + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965', + }]); + expect(server.requests.length).to.equal(2); + + // first is the billing event + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.not.haveOwnProperty('auctions'); + expect(message.billableEvents).to.deep.equal([{ + accountId: 1001, + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + }]); + + // second is auctions + message = JSON.parse(server.requests[1].requestBody); + expect(message).to.haveOwnProperty('auctions'); + expect(message).to.not.haveOwnProperty('billableEvents'); + }); + it('should pass along multiple billing events but filter out duplicates', () => { + // off by default + config.setConfig({ + rubicon: { + dmBilling: { + enabled: true, + vendors: ['vendorName'] + } + } + }); + basicBillingAuction([ + { + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965', + auctionId: MOCK.AUCTION_INIT.auctionId + }, + { + vendor: 'vendorName', + type: 'impression', + billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f', + auctionId: MOCK.AUCTION_INIT.auctionId + }, + { + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965', + auctionId: MOCK.AUCTION_INIT.auctionId + } + ]); + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message).to.haveOwnProperty('auctions'); + expect(message.billableEvents).to.deep.equal([ + { + accountId: 1001, + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965', + auctionId: MOCK.AUCTION_INIT.auctionId + }, + { + accountId: 1001, + vendor: 'vendorName', + type: 'impression', + billingId: '743db6e3-21f2-44d4-917f-cb3488c6076f', + auctionId: MOCK.AUCTION_INIT.auctionId + } + ]); + }); + it('should pass along event right away if no pending auction', () => { + // off by default + config.setConfig({ + rubicon: { + analyticsEventDelay: 0, + dmBilling: { + enabled: true, + vendors: ['vendorName'] + } + } + }); + + events.emit(BILLABLE_EVENT, { + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + }); + expect(server.requests.length).to.equal(1); + const request = server.requests[0]; + const message = JSON.parse(request.requestBody); + expect(message).to.not.haveOwnProperty('auctions'); + expect(message.billableEvents).to.deep.equal([ + { + accountId: 1001, + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + } + ]); + }); + it('should pass along event right away if pending auction but not waiting', () => { + // off by default + config.setConfig({ + rubicon: { + dmBilling: { + enabled: true, + vendors: ['vendorName'], + waitForAuction: false + } + } + }); + // should fire right away, and then auction later + basicBillingAuction([{ + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + }]); + expect(server.requests.length).to.equal(2); + const billingRequest = server.requests[0]; + const billingMessage = JSON.parse(billingRequest.requestBody); + expect(billingMessage).to.not.haveOwnProperty('auctions'); + expect(billingMessage.billableEvents).to.deep.equal([ + { + accountId: 1001, + vendor: 'vendorName', + type: 'auction', + billingId: 'f8558d41-62de-4349-bc7b-2dbee1e69965' + } + ]); + // auction event after + const auctionRequest = server.requests[1]; + const auctionMessage = JSON.parse(auctionRequest.requestBody); + // should not double pass events! + expect(auctionMessage).to.not.haveOwnProperty('billableEvents'); + }); + }); + + describe('getHostNameFromReferer', () => { + it('correctly grabs hostname from an input URL', function () { + let inputUrl = 'https://www.prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.org'); + inputUrl = 'https://www.prebid.com/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('www.prebid.com'); + inputUrl = 'https://prebid.org/some/path?pbjs_debug=true'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + inputUrl = 'http://xn--p8j9a0d9c9a.xn--q9jyb4c/'; + expect(typeof getHostNameFromReferer(inputUrl)).to.equal('string'); + + // not non-UTF char's in query / path which break if noDecodeWholeURL not set + inputUrl = 'https://prebid.org/search_results/%95x%8Em%92%CA/?category=000'; + expect(getHostNameFromReferer(inputUrl)).to.equal('prebid.org'); + }); + }); + + describe(`handle currency conversions`, () => { + const origConvertCurrency = getGlobal().convertCurrency; + afterEach(() => { + if (origConvertCurrency != null) { + getGlobal().convertCurrency = origConvertCurrency; + } else { + delete getGlobal().convertCurrency; + } + }); + + it(`should convert successfully`, () => { + getGlobal().convertCurrency = () => 1.0; + const bidCopy = utils.deepClone(MOCK.BID_RESPONSE); + bidCopy.currency = 'JPY'; + bidCopy.cpm = 100; + + const bidResponseObj = parseBidResponse(bidCopy); + expect(bidResponseObj.conversionError).to.equal(undefined); + expect(bidResponseObj.ogCurrency).to.equal(undefined); + expect(bidResponseObj.ogPrice).to.equal(undefined); + expect(bidResponseObj.bidPriceUSD).to.equal(1.0); + }); + + it(`should catch error and set to zero with conversionError flag true`, () => { + getGlobal().convertCurrency = () => { + throw new Error('I am an error'); + }; + const bidCopy = utils.deepClone(MOCK.BID_RESPONSE); + bidCopy.currency = 'JPY'; + bidCopy.cpm = 100; + + const bidResponseObj = parseBidResponse(bidCopy); + expect(bidResponseObj.conversionError).to.equal(true); + expect(bidResponseObj.ogCurrency).to.equal('JPY'); + expect(bidResponseObj.ogPrice).to.equal(100); + expect(bidResponseObj.bidPriceUSD).to.equal(0); + }); + }); + + describe('onDataDeletionRequest', () => { + it('attempts to delete the magnite cookie when local storage is enabled', () => { + magniteAdapter.onDataDeletionRequest(); + + expect(removeDataFromLocalStorageStub.getCall(0).args[0]).to.equal('mgniSession'); + }); + + it('throws an error if it cannot access the cookie', (done) => { + localStorageIsEnabledStub.returns(false); + try { + magniteAdapter.onDataDeletionRequest(); + } catch (error) { + expect(error.message).to.equal('Unable to access local storage, no data deleted'); + done(); + } + }) + }); +}); diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index 0ce26ab4863..c408f23c4f4 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -3,11 +3,14 @@ import medianetAnalytics from 'modules/medianetAnalyticsAdapter.js'; import * as utils from 'src/utils.js'; import CONSTANTS from 'src/constants.json'; import * as events from 'src/events.js'; +import {clearEvents} from 'src/events.js'; const { EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, NO_BID, BID_TIMEOUT, AUCTION_END, SET_TARGETING, BID_WON } } = CONSTANTS; +const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; + const MOCK = { Ad_Units: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, 'bids': [], 'ext': {'prop1': 'value1'}}], MULTI_FORMAT_TWIN_AD_UNITS: [{'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'banner': {'sizes': [[300, 250]]}, 'native': {'image': {'required': true, 'sizes': [150, 50]}}}, 'bids': [], 'ext': {'prop1': 'value1'}}, {'code': 'div-gpt-ad-1460505748561-0', 'mediaTypes': {'video': {'playerSize': [640, 480], 'context': 'instream'}}, 'bids': [], 'ext': {'prop1': 'value1'}}], @@ -22,11 +25,15 @@ const MOCK = { SET_TARGETING: {'div-gpt-ad-1460505748561-0': {'prebid_test': '1', 'hb_format': 'banner', 'hb_source': 'client', 'hb_size': '300x250', 'hb_pb': '2.00', 'hb_adid': '3e6e4bce5c8fb3', 'hb_bidder': 'medianet', 'hb_format_medianet': 'banner', 'hb_source_medianet': 'client', 'hb_size_medianet': '300x250', 'hb_pb_medianet': '2.00', 'hb_adid_medianet': '3e6e4bce5c8fb3', 'hb_bidder_medianet': 'medianet'}}, NO_BID_SET_TARGETING: {'div-gpt-ad-1460505748561-0': {}}, BID_WON: {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, + BID_WON_2: {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}, + BID_WON_UNKNOWN: {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'statusMessage': 'Bid available', 'adId': '3e6e4bce5c8fkk', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}, NO_BID: {'bidder': 'medianet', 'params': {'cid': 'test123', 'crid': '451466393', 'site': {}}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '303fa0c6-682f-4aea-8e4a-dc68f0d5c7d5', 'sizes': [[300, 250], [300, 600]], 'bidId': '28248b0e6aece2', 'bidderRequestId': '13fccf3809fe43', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}, BID_TIMEOUT: [{'bidId': '28248b0e6aece2', 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'params': [{'cid': 'test123', 'crid': '451466393', 'site': {}}, {'cid': '8CUX0H51P', 'crid': '451466393', 'site': {}}], 'timeout': 6}], - BIDS_SAME_REQ_DIFF_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fgg', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], - BIDS_SAME_REQ_EQUAL_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fgg', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 286, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}] -} + BIDS_SAME_REQ_DIFF_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.10, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], + BIDS_SAME_REQ_EQUAL_CPM: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 286, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'status': 'rendered', 'params': [{'cid': 'test123', 'crid': '451466393'}]}], + BID_RESPONSES: [{'bidderCode': 'medianet', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb3', 'requestId': '28248b0e6aece2', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 2.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 266, 'pbLg': '2.00', 'pbMg': '2.20', 'pbHg': '2.29', 'pbAg': '2.25', 'pbDg': '2.29', 'pbCg': '2.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'medianet', 'hb_adid': '3e6e4bce5c8fb3', 'hb_pb': '2.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'cid': 'test123', 'crid': '451466393'}]}, {'bidderCode': 'appnexus', 'width': 300, 'height': 250, 'adId': '3e6e4bce5c8fb4', 'requestId': '28248b0e6aecd5', 'mediaType': 'banner', 'source': 'client', 'ext': {'pvid': 123, 'crid': '321'}, 'no_bid': false, 'cpm': 1.299, 'ad': 'AD_CODE', 'ttl': 180, 'creativeId': 'Test1', 'netRevenue': true, 'currency': 'USD', 'dfp_id': 'div-gpt-ad-1460505748561-0', 'originalCpm': 1.1495, 'originalCurrency': 'USD', 'floorData': {'floorValue': 1.1, 'floorRule': 'banner'}, 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'responseTimestamp': 1584563606009, 'requestTimestamp': 1584563605743, 'bidder': 'medianet', 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'timeToRespond': 278, 'pbLg': '1.00', 'pbMg': '1.20', 'pbHg': '1.29', 'pbAg': '1.25', 'pbDg': '1.29', 'pbCg': '1.00', 'size': '300x250', 'adserverTargeting': {'hb_bidder': 'appnexus', 'hb_adid': '3e6e4bce5c8fb4', 'hb_pb': '1.00', 'hb_size': '300x250', 'hb_source': 'client', 'hb_format': 'banner', 'prebid_test': 1}, 'params': [{'publisherId': 'test123', 'placementId': '451466393'}]}], + BID_REQUESTS: [{'bidderCode': 'medianet', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'medianet', 'params': {'cid': 'TEST_CID', 'crid': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aece2', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}, {'bidderCode': 'appnexus', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'bids': [{'bidder': 'appnexus', 'params': {'publisherId': 'TEST_CID', 'placementId': '451466393'}, 'mediaTypes': {'banner': {'sizes': [[300, 250]], 'ext': ['asdads']}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250]], 'bidId': '28248b0e6aecd5', 'auctionId': '8e0d5245-deb3-406c-96ca-9b609e077ff7', 'src': 'client'}], 'auctionStart': 1584563605739, 'timeout': 6000, 'uspConsent': '1YY', 'start': 1584563605743}] +}; function performAuctionWithFloorConfig() { events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT_WITH_FLOOR, {adUnits: MOCK.Ad_Units})); @@ -87,6 +94,14 @@ function performStandardAuctionMultiBidWithSameRequestId(bidRespArray) { events.emit(BID_WON, MOCK.BID_WON); } +function performStandardAuctionMultiBidResponseNoWin() { + events.emit(AUCTION_INIT, Object.assign({}, MOCK.AUCTION_INIT, {adUnits: MOCK.Ad_Units})); + MOCK.BID_REQUESTS.forEach(bidReq => events.emit(BID_REQUESTED, bidReq)); + MOCK.BID_RESPONSES.forEach(bidResp => events.emit(BID_RESPONSE, bidResp)); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); +} + function getQueryData(url, decode = false) { const queryArgs = url.split('?')[1].split('&'); return queryArgs.reduce((data, arg) => { @@ -114,6 +129,11 @@ describe('Media.net Analytics Adapter', function() { cid: CUSTOMER_ID } } + + before(() => { + clearEvents(); + }); + beforeEach(function () { sandbox = sinon.sandbox.create(); }); @@ -280,25 +300,53 @@ describe('Media.net Analytics Adapter', function() { it('should pick winning bid if multibids with same request id', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM); let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fgg'); + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); medianetAnalytics.clearlogsQueue(); const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_DIFF_CPM).reverse(); performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray); winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fgg'); + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); }); it('should pick winning bid if multibids with same request id and equal cpm', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_EQUAL_CPM); let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fgg'); + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); medianetAnalytics.clearlogsQueue(); const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_EQUAL_CPM).reverse(); performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray); winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; - expect(winningBid.adid).equals('3e6e4bce5c8fgg'); + expect(winningBid.adid).equals('3e6e4bce5c8fb3'); + }); + + it('should pick single winning bid per bid won', function() { + performStandardAuctionMultiBidResponseNoWin(); + const queue = medianetAnalytics.getlogsQueue(); + queue.length = 0; + + events.emit(BID_WON, MOCK.BID_WON); + let winningBids = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); + expect(winningBids[0].adid).equals(MOCK.BID_WON.adId); + expect(winningBids.length).equals(1); + events.emit(BID_WON, MOCK.BID_WON_2); + winningBids = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); + expect(winningBids[1].adid).equals(MOCK.BID_WON_2.adId); + expect(winningBids.length).equals(2); + }); + + it('should ignore unknown winning bid and log error', function() { + performStandardAuctionMultiBidResponseNoWin(); + const queue = medianetAnalytics.getlogsQueue(); + queue.length = 0; + + events.emit(BID_WON, MOCK.BID_WON_UNKNOWN); + let winningBids = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)); + let errors = medianetAnalytics.getErrorQueue().map((log) => getQueryData(log)); + expect(winningBids.length).equals(0); + expect(errors.length).equals(1); + expect(errors[0].event).equals(ERROR_WINNING_BID_ABSENT); }); }); }); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 7333900eedf..9180bbad68c 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -856,25 +856,50 @@ let VALID_BID_REQUEST = [{ cid: '8CUV090' } }, + VALID_PARAMS_AAX = { + bidder: 'aax', + params: { + cid: 'AAXG123' + } + }, PARAMS_MISSING = { bidder: 'medianet', }, + PARAMS_MISSING_AAX = { + bidder: 'aax', + }, PARAMS_WITHOUT_CID = { bidder: 'medianet', params: {} }, + PARAMS_WITHOUT_CID_AAX = { + bidder: 'aax', + params: {} + }, PARAMS_WITH_INTEGER_CID = { bidder: 'medianet', params: { cid: 8867587 } }, + PARAMS_WITH_INTEGER_CID_AAX = { + bidder: 'aax', + params: { + cid: 8867587 + } + }, PARAMS_WITH_EMPTY_CID = { bidder: 'medianet', params: { cid: '' } }, + PARAMS_WITH_EMPTY_CID_AAX = { + bidder: 'aax', + params: { + cid: '' + } + }, SYNC_OPTIONS_BOTH_ENABLED = { iframeEnabled: true, pixelEnabled: true, @@ -1571,4 +1596,55 @@ describe('Media.net bid adapter', function () { expect(requestObj.imp[0].hasOwnProperty('bidfloors')).to.equal(true); }); }); + + describe('isBidRequestValid aax', function () { + it('should accept valid bid params', function () { + let isValid = spec.isBidRequestValid(VALID_PARAMS_AAX); + expect(isValid).to.equal(true); + }); + + it('should reject bid if cid is not present', function () { + let isValid = spec.isBidRequestValid(PARAMS_WITHOUT_CID_AAX); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is not a string', function () { + let isValid = spec.isBidRequestValid(PARAMS_WITH_INTEGER_CID_AAX); + expect(isValid).to.equal(false); + }); + + it('should reject bid if cid is a empty string', function () { + let isValid = spec.isBidRequestValid(PARAMS_WITH_EMPTY_CID_AAX); + expect(isValid).to.equal(false); + }); + + it('should have missing params', function () { + let isValid = spec.isBidRequestValid(PARAMS_MISSING_AAX); + expect(isValid).to.equal(false); + }); + }); + + describe('interpretResponse aax', function () { + it('should not push response if no-bid', function () { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_NOBID, []); + expect(bids).to.deep.equal(validBids); + }); + + it('should have empty bid response', function() { + let bids = spec.interpretResponse(SERVER_RESPONSE_NOBODY, []); + expect(bids).to.deep.equal([]); + }); + + it('should have valid bids', function () { + let bids = spec.interpretResponse(SERVER_RESPONSE_VALID_BID, []); + expect(bids).to.deep.equal(SERVER_VALID_BIDS); + }); + + it('should have empty bid list', function() { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_EMPTY_BIDLIST, []); + expect(bids).to.deep.equal(validBids); + }); + }); }); diff --git a/test/spec/modules/medianetRtdProvider_spec.js b/test/spec/modules/medianetRtdProvider_spec.js index 7d73ecd5d44..f9d4ef7c2cf 100644 --- a/test/spec/modules/medianetRtdProvider_spec.js +++ b/test/spec/modules/medianetRtdProvider_spec.js @@ -66,12 +66,12 @@ describe('medianet realtime module', function () { describe('getTargeting should work correctly', function () { it('should return empty if not loaded', function () { window.mnjs.loaded = false; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([]), {}); + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData([], {}, {}, {}), {}); }); it('should return ad unit codes when ad units are present', function () { const adUnitCodes = ['code1', 'code2']; - assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes), { + assert.deepEqual(medianetRTD.medianetRtdModule.getTargetingData(adUnitCodes, {}, {}, {}), { code1: {'mnadc': 'code1'}, code2: {'mnadc': 'code2'}, }); @@ -79,7 +79,7 @@ describe('medianet realtime module', function () { it('should call mnjs.getTargetingData if loaded', function () { window.mnjs.loaded = true; - medianetRTD.medianetRtdModule.getTargetingData([]); + medianetRTD.medianetRtdModule.getTargetingData([], {}, {}, {}); assert.equal(getTargetingDataSpy.called, true); }); }); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index eeab047a405..346d02d91d0 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -78,7 +78,8 @@ describe('MediaSquare bid adapter tests', function () { owner: 'test', code: 'publishername_atf_desktop_rg_pave' }, - getFloor: function (a) { return { currency: 'EUR', floor: 1.0 }; }, + sizes: [[300, 250]], + getFloor: function (a) { return { currency: 'USD', floor: 1.0 }; }, }]; var BID_RESPONSE = {'body': { 'responses': [{ @@ -142,7 +143,7 @@ describe('MediaSquare bid adapter tests', function () { const requestfloor = spec.buildRequests(FLOORS_PARAMS, DEFAULT_OPTIONS); const responsefloor = JSON.parse(requestfloor.data); expect(responsefloor.codes[0]).to.have.property('floor').exist; - expect(responsefloor.codes[0].floor).to.have.property('floor').and.to.equal(1.0); + expect(responsefloor.codes[0].floor).to.have.property('300x250').and.to.have.property('floor').and.to.equal(1); }); it('Verify parse response', function () { @@ -239,6 +240,7 @@ describe('MediaSquare bid adapter tests', function () { const bid = response[0]; expect(bid).to.have.property('vastXml'); expect(bid).to.have.property('vastUrl'); + expect(bid).to.have.property('renderer'); delete BID_RESPONSE.body.responses[0].video; }); }); diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index 34ad29b3e92..c79cad6245d 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -31,6 +31,10 @@ describe('Mgid bid adapter', function () { const mgid_ver = spec.VERSION; const utcOffset = (new Date()).getTimezoneOffset().toString(); + it('should expose gvlid', function() { + expect(spec.gvlid).to.equal(358) + }); + describe('isBidRequestValid', function () { let bid = { 'adUnitCode': 'div', @@ -541,6 +545,52 @@ describe('Mgid bid adapter', function () { 'data': '{"site":{"domain":"' + domain + '","page":"' + page + '"},"cur":["USD"],"geo":{"utcoffset":' + utcOffset + '},"device":{"ua":"' + ua + '","js":1,"dnt":' + dnt + ',"h":' + screenHeight + ',"w":' + screenWidth + ',"language":"' + lang + '"},"ext":{"mgid_ver":"' + mgid_ver + '","prebid_ver":"' + version + '"},"imp":[{"tagid":"2/div","secure":' + secure + ',"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}]}', }); }); + it('should proper handle ortb2 data', function () { + let bid = Object.assign({}, abid); + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + let bidRequests = [bid]; + + let bidderRequest = { + ortb2: { + site: { + content: { + data: [{ + name: 'mgid.com', + ext: { + segtax: 1, + }, + segment: [ + {id: '123'}, + {id: '456'}, + ], + }] + } + }, + user: { + data: [{ + name: 'mgid.com', + ext: { + segtax: 2, + }, + segment: [ + {'id': '789'}, + {'id': '987'}, + ], + }] + } + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).deep.equal('https://prebid.mgid.com/prebid/1'); + expect(request.method).deep.equal('POST'); + const data = JSON.parse(request.data); + expect(data.ext).deep.include(bidderRequest.ortb2); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/mgidRtdProvider_spec.js b/test/spec/modules/mgidRtdProvider_spec.js new file mode 100644 index 00000000000..4f70b4d8b7c --- /dev/null +++ b/test/spec/modules/mgidRtdProvider_spec.js @@ -0,0 +1,366 @@ +import { mgidSubmodule, storage } from '../../../modules/mgidRtdProvider.js'; +import {expect} from 'chai'; +import * as refererDetection from '../../../src/refererDetection'; + +describe('Mgid RTD submodule', () => { + let server; + let clock; + let getRefererInfoStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + server = sinon.fakeServer.create(); + + clock = sinon.useFakeTimers(); + + getRefererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + getRefererInfoStub.returns({ + canonicalUrl: 'https://www.test.com/abc' + }); + + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').returns('qwerty654321'); + }); + + afterEach(() => { + server.restore(); + clock.restore(); + getRefererInfoStub.restore(); + getDataFromLocalStorageStub.restore(); + }); + + it('init is successfull, when clientSiteId is defined', () => { + expect(mgidSubmodule.init({params: {clientSiteId: 123}})).to.be.true; + }); + + it('init is unsuccessfull, when clientSiteId is not defined', () => { + expect(mgidSubmodule.init({})).to.be.false; + }); + + it('getBidRequestData send all params to our endpoint and succesfully modifies ortb2', () => { + const responseObj = { + userSegments: ['100', '200'], + userSegtax: 5, + siteSegments: ['300', '400'], + siteSegtax: 7, + muid: 'qwerty654321', + }; + + let reqBidsConfigObj = { + ortb2Fragments: { + global: { + site: { + content: { + language: 'en', + } + } + }, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + { + gdpr: { + gdprApplies: true, + consentString: 'testConsent', + }, + usp: '1YYY', + } + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify(responseObj) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq('servicer.mgid.com'); + expect(requestUrl.searchParams.get('gdprApplies')).to.be.eq('true'); + expect(requestUrl.searchParams.get('consentData')).to.be.eq('testConsent'); + expect(requestUrl.searchParams.get('uspString')).to.be.eq('1YYY'); + expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321'); + expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123'); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc'); + expect(requestUrl.searchParams.get('cxlang')).to.be.eq('en'); + + assert.deepInclude( + reqBidsConfigObj.ortb2Fragments.global, + { + site: { + content: { + language: 'en', + data: [ + { + name: 'www.mgid.com', + ext: { + segtax: 7 + }, + segment: [ + { id: '300' }, + { id: '400' }, + ] + } + ], + } + }, + user: { + data: [ + { + name: 'www.mgid.com', + ext: { + segtax: 5 + }, + segment: [ + { id: '100' }, + { id: '200' }, + ] + } + ], + }, + }); + }); + + it('getBidRequestData doesn\'t send params (consent and cxlang), if we haven\'t received them', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq('servicer.mgid.com'); + expect(requestUrl.searchParams.get('gdprApplies')).to.be.null; + expect(requestUrl.searchParams.get('consentData')).to.be.null; + expect(requestUrl.searchParams.get('uspString')).to.be.null; + expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321'); + expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123'); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc'); + expect(requestUrl.searchParams.get('cxlang')).to.be.null; + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData send gdprApplies event if it is false', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + { + gdpr: { + gdprApplies: false, + consentString: 'testConsent', + }, + usp: '1YYY', + } + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.host).to.be.eq('servicer.mgid.com'); + expect(requestUrl.searchParams.get('gdprApplies')).to.be.eq('false'); + expect(requestUrl.searchParams.get('consentData')).to.be.eq('testConsent'); + expect(requestUrl.searchParams.get('uspString')).to.be.eq('1YYY'); + expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321'); + expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123'); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc'); + expect(requestUrl.searchParams.get('cxlang')).to.be.null; + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData use og:url for cxurl, if it is available', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + let metaStub = sinon.stub(document, 'getElementsByTagName').returns([ + { getAttribute: () => 'og:test', content: 'fake' }, + { getAttribute: () => 'og:url', content: 'https://realOgUrl.com/' } + ]); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://realOgUrl.com/'); + expect(onDone.calledOnce).to.be.true; + + metaStub.restore(); + }); + + it('getBidRequestData use topMostLocation for cxurl, if nothing else left', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + getRefererInfoStub.returns({ + topmostLocation: 'https://www.test.com/topMost' + }); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + JSON.stringify({}) + ); + + const requestUrl = new URL(server.requests[0].url); + expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/topMost'); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response is broken', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 200, + {'Content-Type': 'application/json'}, + '{' + ); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response status is not 200', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 204, + {'Content-Type': 'application/json'}, + '{}' + ); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response results in error', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123}}, + {} + ); + + server.requests[0].respond( + 500, + {'Content-Type': 'application/json'}, + '{}' + ); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); + + it('getBidRequestData won\'t modify ortb2 if response time hits timeout', () => { + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + } + }; + + let onDone = sinon.stub(); + + mgidSubmodule.getBidRequestData( + reqBidsConfigObj, + onDone, + {params: {clientSiteId: 123, timeout: 500}}, + {} + ); + + clock.tick(510); + + assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {}); + expect(onDone.calledOnce).to.be.true; + }); +}); diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/modules/microadBidAdapter_spec.js index cca337c83f2..98edfce20bc 100644 --- a/test/spec/modules/microadBidAdapter_spec.js +++ b/test/spec/modules/microadBidAdapter_spec.js @@ -330,6 +330,54 @@ describe('microadBidAdapter', () => { }) }) }) + + Object.entries({ + 'ID5 ID': { + userId: {id5id: {uid: 'id5id-sample'}}, + userIdAsEids: [ + { + source: 'id5-sync.com', + uids: [{id: 'id5id-sample', aType: 1, ext: {linkType: 2, abTestingControlGroup: false}}] + } + ], + expected: { + aids: JSON.stringify([{type: 8, id: 'id5id-sample', ext: {linkType: 2, abTestingControlGroup: false}}]) + } + }, + 'Unified ID': { + userId: {tdid: 'unified-sample'}, + userIdAsEids: [ + { + source: 'adserver.org', + uids: [{id: 'unified-sample', aType: 1, ext: {rtiPartner: 'TDID'}}] + } + ], + expected: {aids: JSON.stringify([{type: 9, id: 'unified-sample', ext: {rtiPartner: 'TDID'}}])} + }, + 'not add': { + userId: {id5id: {uid: 'id5id-sample'}}, + userIdAsEids: [], + expected: { + aids: JSON.stringify([{type: 8, id: 'id5id-sample'}]) + } + } + }).forEach(([test, arg]) => { + it(`should ${test} ext if it is available in request parameters`, () => { + const bidRequestWithUserId = { + ...bidRequestTemplate, + userId: arg.userId, + userIdAsEids: arg.userIdAsEids + } + const requests = spec.buildRequests([bidRequestWithUserId], bidderRequest) + requests.forEach((request) => { + expect(request.data).to.deep.equal({ + ...expectedResultTemplate, + cbt: request.data.cbt, + ...arg.expected + }) + }) + }); + }) }); describe('interpretResponse', () => { diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js new file mode 100644 index 00000000000..de38554f010 --- /dev/null +++ b/test/spec/modules/minutemediaplusBidAdapter_spec.js @@ -0,0 +1,519 @@ +import { expect } from 'chai'; +import { + spec as adapter, + SUPPORTED_ID_SYSTEMS, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from 'modules/minutemediaplusBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { version } from 'package.json'; +import { useFakeTimers } from 'sinon'; +import { BANNER, VIDEO } from '../../../src/mediaTypes'; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER] +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['minutemedia-prebid.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('MinuteMediaPlus Bid Adapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + prebidVersion: version, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + usPrivacy: 'consent_string', + sizes: ['300x250', '300x600'], + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({ price: 1, ad: '' }); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['minutemedia-prebid.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + Object.keys(SUPPORTED_ID_SYSTEMS).forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return { lipbid: id }; + case 'parrableId': + return { eid: id }; + case 'id5id': + return { uid: id }; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({ 'c_id': '1' }); + const pid = extractPID({ 'p_id': '1' }); + const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({ 'cID': '1' }); + const pid = extractPID({ 'Pid': '2' }); + const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mmplus: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const { value, created } = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({ event: 'send' }); + const { event } = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index 4cd4c92281b..4d70e6f7071 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -5,6 +5,9 @@ import { getMediaWildcardPrices, sizeToString, parseFloorPriceData, + getPageUrlFromBidRequest, + hasProtocol, + addProtocol, } from '../../../modules/nativoBidAdapter' describe('bidDataMap', function () { @@ -647,3 +650,84 @@ describe('parseFloorPriceData', () => { }) }) }) + +describe('hasProtocol', () => { + it('https://www.testpage.com', () => { + expect(hasProtocol('https://www.testpage.com')).to.be.true + }) + it('http://www.testpage.com', () => { + expect(hasProtocol('http://www.testpage.com')).to.be.true + }) + it('//www.testpage.com', () => { + expect(hasProtocol('//www.testpage.com')).to.be.false + }) + it('www.testpage.com', () => { + expect(hasProtocol('www.testpage.com')).to.be.false + }) + it('httpsgsjhgflih', () => { + expect(hasProtocol('httpsgsjhgflih')).to.be.false + }) +}) + +describe('addProtocol', () => { + it('www.testpage.com', () => { + expect(addProtocol('www.testpage.com')).to.be.equal('https://www.testpage.com') + }) + it('//www.testpage.com', () => { + expect(addProtocol('//www.testpage.com')).to.be.equal('https://www.testpage.com') + }) + it('http://www.testpage.com', () => { + expect(addProtocol('http://www.testpage.com')).to.be.equal('http://www.testpage.com') + }) + it('https://www.testpage.com', () => { + expect(addProtocol('https://www.testpage.com')).to.be.equal('https://www.testpage.com') + }) +}) + +describe('getPageUrlFromBidRequest', () => { + const bidRequest = {} + + beforeEach(() => { + bidRequest.params = {} + }) + + it('Returns undefined for no url param', () => { + const url = getPageUrlFromBidRequest(bidRequest) + expect(url).to.be.undefined + }) + + it('@testUrl', () => { + const url = getPageUrlFromBidRequest(bidRequest) + expect(url).to.be.undefined + }) + + it('https://www.testpage.com', () => { + bidRequest.params.url = 'https://www.testpage.com' + const url = getPageUrlFromBidRequest(bidRequest) + expect(url).not.to.be.undefined + }) + + it('https://www.testpage.com/test/path', () => { + bidRequest.params.url = 'https://www.testpage.com/test/path' + const url = getPageUrlFromBidRequest(bidRequest) + expect(url).not.to.be.undefined + }) + + it('www.testpage.com', () => { + bidRequest.params.url = 'www.testpage.com' + const url = getPageUrlFromBidRequest(bidRequest) + expect(url).not.to.be.undefined + }) + + it('http://www.testpage.com', () => { + bidRequest.params.url = 'http://www.testpage.com' + const url = getPageUrlFromBidRequest(bidRequest) + expect(url).not.to.be.undefined + }) + + it('//www.testpage.com', () => { + bidRequest.params.url = '//www.testpage.com' + const url = getPageUrlFromBidRequest(bidRequest) + expect(url).not.to.be.undefined + }) +}) diff --git a/test/spec/modules/neuwoRtdProvider_spec.js b/test/spec/modules/neuwoRtdProvider_spec.js new file mode 100644 index 00000000000..7f47fba072b --- /dev/null +++ b/test/spec/modules/neuwoRtdProvider_spec.js @@ -0,0 +1,123 @@ +import { server } from 'test/mocks/xhr.js'; +import * as neuwo from 'modules/neuwoRtdProvider'; + +const PUBLIC_TOKEN = 'public_key_0000'; +const config = () => ({ + params: { + publicToken: PUBLIC_TOKEN + } +}) + +const apiReturns = () => ({ + somethingExtra: { object: true }, + marketing_categories: { + iab_tier_1: [ + { ID: 'IAB21', label: 'Real Estate', relevance: '0.45699' } + ] + } +}) + +const TAX_ID = '441' + +/** + * Object generator, like above, written using alternative techniques + * @returns object with predefined (expected) bidsConfig fields + */ +function bidsConfiglike() { + return Object.assign({}, { + ortb2Fragments: { global: {} } + }) +} + +describe('neuwoRtdProvider', function () { + describe('neuwoRtdModule', function () { + it('initializes', function () { + expect(neuwo.neuwoRtdModule.init(config())).to.be.true; + }) + it('init needs that public token', function () { + expect(neuwo.neuwoRtdModule.init()).to.be.false; + }) + + describe('segment picking', function () { + it('handles bad inputs', function () { + expect(neuwo.pickSegments()).to.be.an('array').that.is.empty; + expect(neuwo.pickSegments('technically also an array')).to.be.an('array').that.is.empty; + expect(neuwo.pickSegments({ bad_object: 'bad' })).to.be.an('array').that.is.empty; + }) + it('handles malformations', function () { + let result = neuwo.pickSegments([{something_wrong: true}, null, { ID: 'IAB19-20' }, { id: 'IAB3-1', ID: 'IAB9-20' }]) + expect(result[0].id).to.equal('631') + expect(result[1].id).to.equal('58') + expect(result.length).to.equal(2) + }) + }) + + describe('topic injection', function () { + it('mutates bidsConfig', function () { + let topics = apiReturns() + let bidsConfig = bidsConfiglike() + neuwo.injectTopics(topics, bidsConfig, () => { }) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) + expect(bidsConfig.ortb2Fragments.global.site.cattax, 'category taxonomy code for pagecat').to.equal(6) // CATTAX_IAB + expect(bidsConfig.ortb2Fragments.global.site.pagecat[0], 'category taxonomy code for pagecat').to.equal(TAX_ID) + }) + + it('handles malformed responses', function () { + let topics = { message: 'Forbidden' } + let bidsConfig = bidsConfiglike() + neuwo.injectTopics(topics, bidsConfig, () => { }) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bidsConfig.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + + topics = '404 wouldn\'t really even show up for injection' + let bdsConfig = bidsConfiglike() + neuwo.injectTopics(topics, bdsConfig, () => { }) + expect(bdsConfig.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bdsConfig.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + + topics = undefined + let bdsConfigE = bidsConfiglike() + neuwo.injectTopics(topics, bdsConfigE, () => { }) + expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bdsConfigE.ortb2Fragments.global.site.content.data[0].segment, 'length of segment(s) in content.data').to.be.an('array').that.is.empty; + }) + }) + + describe('fragment addition', function () { + it('mutates input objects', function () { + let alphabet = { a: { b: { c: {} } } } + neuwo.addFragment(alphabet.a.b.c, 'd.e.f', { g: 'h' }) + expect(alphabet.a.b.c.d.e.f.g).to.equal('h') + }) + }) + + describe('getBidRequestData', function () { + it('forms requests properly and mutates input bidsConfig', function () { + let bids = bidsConfiglike() + let conf = config() + // control xhr api request target for testing + conf.params.argUrl = 'https://publisher.works/article.php?get=horrible_url_for_testing&id=5' + + neuwo.getBidRequestData(bids, () => { }, conf, 'any consent data works, clearly') + + let request = server.requests[0]; + expect(request.url).to.be.a('string').that.includes(conf.params.publicToken) + expect(request.url).to.include(encodeURIComponent(conf.params.argUrl)) + request.respond(200, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify(apiReturns())); + + expect(bids.ortb2Fragments.global.site.content.data[0].name, 'name of first content data object').to.equal(neuwo.DATA_PROVIDER) + expect(bids.ortb2Fragments.global.site.content.data[0].segment[0].id, 'id of first segment in content.data').to.equal(TAX_ID) + }) + + it('accepts detail not available result', function () { + let bidsConfig = bidsConfiglike() + let comparison = bidsConfiglike() + neuwo.getBidRequestData(bidsConfig, () => { }, config(), 'consensually') + let request = server.requests[0]; + request.respond(404, { 'Content-Type': 'application/json; encoding=UTF-8' }, JSON.stringify({ detail: 'Basically first time seeing this' })); + expect(bidsConfig).to.deep.equal(comparison) + }) + }) + }) +}) diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 36920245070..7f40e7d89f8 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -28,6 +28,34 @@ describe('nextMillenniumBidAdapterTests', function() { } ]; + const serverResponse = { + body: { + id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', + seatbid: [ + { + bid: [ + { + id: '7457329903666272789', + price: 0.5, + adm: 'Hello! It\'s a test ad!', + adid: '96846035', + adomain: ['test.addomain.com'], + w: 300, + h: 250 + } + ] + } + ], + cur: 'USD', + ext: { + sync: { + image: ['urlA?gdpr={{.GDPR}}'], + iframe: ['urlB'], + } + } + } + }; + const bidRequestDataGI = [ { adUnitCode: 'test-banner-gi', @@ -96,6 +124,47 @@ describe('nextMillenniumBidAdapterTests', function() { expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); }); + it('Test getUserSyncs function', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + } + let userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.equal('urlA?gdpr=1'); + + syncOptions.iframeEnabled = true; + syncOptions.pixelEnabled = false; + userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.equal('iframe'); + expect(userSync[0].url).to.equal('urlB'); + }); + + it('Test getUserSyncs with no response', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, [], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array') + expect(userSync[0].type).to.equal('iframe') + expect(userSync[0].url).to.equal('https://cookies.nextmillmedia.com/sync?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&type=iframe') + }) + + it('Test getUserSyncs function if GDPR is undefined', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + } + + let userSync = spec.getUserSyncs(syncOptions, [serverResponse], undefined, bidRequestData[0].uspConsent); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.equal('image'); + expect(userSync[0].url).to.equal('urlA?gdpr=0'); + }); + it('Request params check without GDPR Consent', function () { delete bidRequestData[0].gdprConsent const request = spec.buildRequests(bidRequestData, bidRequestData[0]); @@ -137,50 +206,16 @@ describe('nextMillenniumBidAdapterTests', function() { it('Check if imp object was added', function() { const request = spec.buildRequests(bidRequestData) expect(JSON.parse(request[0].data).imp).to.be.an('array') - }) + }); it('Check if imp prebid stored id is correct', function() { const request = spec.buildRequests(bidRequestData) const requestData = JSON.parse(request[0].data); const storedReqId = requestData.ext.prebid.storedrequest.id; expect(requestData.imp[0].ext.prebid.storedrequest.id).to.equal(storedReqId) - }) - - it('Test getUserSyncs function', function () { - const syncOptions = { - 'iframeEnabled': true - } - const userSync = spec.getUserSyncs(syncOptions); - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.exist; - expect(userSync[0].url).to.exist; - expect(userSync[0].type).to.be.equal('iframe'); - expect(userSync[0].url).to.be.equal('https://statics.nextmillmedia.com/load-cookie.html?v=4'); }); it('validate_response_params', function() { - const serverResponse = { - body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', - seatbid: [ - { - bid: [ - { - id: '7457329903666272789', - price: 0.5, - adm: 'Hello! It\'s a test ad!', - adid: '96846035', - adomain: ['test.addomain.com'], - w: 300, - h: 250 - } - ] - } - ], - cur: 'USD' - } - }; - let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); expect(bids).to.have.lengthOf(1); @@ -275,4 +310,137 @@ describe('nextMillenniumBidAdapterTests', function() { expect(bid.height).to.equal(250); expect(bid.currency).to.equal('USD'); }); + + it('Check function of getting URL for sending statistics data', function() { + const dataForTests = [ + { + eventName: 'bidRequested', + bid: { + bidderCode: 'appnexus', + bids: [{bidder: 'appnexus', params: {}}], + }, + + expected: undefined, + }, + + { + eventName: 'bidRequested', + bid: { + bidderCode: 'appnexus', + bids: [{bidder: 'appnexus', params: {placement_id: '807'}}], + }, + + expected: undefined, + }, + + { + eventName: 'bidRequested', + bid: { + bidderCode: 'nextMillennium', + bids: [{bidder: 'nextMillennium', params: {placement_id: '807'}}], + }, + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidRequested&bidder=nextMillennium&source=pbjs&placements=807', + }, + + { + eventName: 'bidRequested', + bid: { + bidderCode: 'nextMillennium', + bids: [ + {bidder: 'nextMillennium', params: {placement_id: '807'}}, + {bidder: 'nextMillennium', params: {placement_id: '111'}}, + ], + }, + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidRequested&bidder=nextMillennium&source=pbjs&placements=807;111', + }, + + { + eventName: 'bidRequested', + bid: { + bidderCode: 'nextMillennium', + bids: [{bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}], + }, + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidRequested&bidder=nextMillennium&source=pbjs&groups=123', + }, + + { + eventName: 'bidRequested', + bid: { + bidderCode: 'nextMillennium', + bids: [ + {bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}, + {bidder: 'nextMillennium', params: {group_id: '456'}}, + {bidder: 'nextMillennium', params: {placement_id: '222'}}, + ], + }, + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidRequested&bidder=nextMillennium&source=pbjs&groups=123;456&placements=222', + }, + + { + eventName: 'bidResponse', + bid: { + bidderCode: 'appnexus', + }, + + expected: undefined, + }, + + { + eventName: 'bidResponse', + bid: { + bidderCode: 'nextMillennium', + params: {placement_id: '807'}, + }, + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidResponse&bidder=nextMillennium&source=pbjs&placements=807', + }, + + { + eventName: 'noBid', + bid: { + bidder: 'appnexus', + }, + + expected: undefined, + }, + + { + eventName: 'noBid', + bid: { + bidder: 'nextMillennium', + params: {placement_id: '807'}, + }, + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=noBid&bidder=nextMillennium&source=pbjs&placements=807', + }, + + { + eventName: 'bidTimeout', + bid: { + bidder: 'appnexus', + }, + + expected: undefined, + }, + + { + eventName: 'bidTimeout', + bid: { + bidder: 'nextMillennium', + params: {placement_id: '807'}, + }, + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidTimeout&bidder=nextMillennium&source=pbjs&placements=807', + }, + ]; + + for (let {eventName, bid, expected} of dataForTests) { + const url = spec.getUrlPixelMetric(eventName, bid); + expect(url).to.equal(expected); + }; + }) }); diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 1a695e5f8b3..7645ee59f63 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -6,70 +6,39 @@ import * as utils from 'src/utils.js'; import { requestBidsHook } from 'modules/consentManagement.js'; describe('Nexx360 bid adapter tests', function () { - const DISPLAY_BID_REQUEST = [{ - 'bidder': 'nexx360', - 'params': { - 'account': '1067', - 'tagId': 'luvxjvgn' + const DISPLAY_BID_REQUEST = { + 'id': '77b3f21a-e0df-4495-8bce-4e8a1d2309c1', + 'imp': [ + {'id': '2b4d8fc1c1c7ea', + 'tagid': 'div-1', + 'ext': {'divId': 'div-1', 'nexx360': {'account': '1067', 'tag_id': 'luvxjvgn'}}, + 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}], 'topframe': 1}}, {'id': '38fc428ab96638', 'tagid': 'div-2', 'ext': {'divId': 'div-2', 'nexx360': {'account': '1067', 'tag_id': 'luvxjvgn'}}, 'banner': {'format': [{'w': 728, 'h': 90}, {'w': 970, 'h': 250}], 'topframe': 1}}], + 'cur': ['USD'], + 'at': 1, + 'tmax': 3000, + 'site': {'page': 'https://test.nexx360.io/adapter/index.html?nexx360_test=1', 'domain': 'test.nexx360.io'}, + 'regs': {'coppa': 0, 'ext': {'gdpr': 1}}, + 'device': { + 'dnt': 0, + 'h': 844, + 'w': 390, + 'ua': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1', + 'language': 'fr' }, - 'userId': { - 'id5id': { - 'uid': 'ID5*hQ5WobYI9Od4u52qpaXVKHhxUa4DsOWRAlvaFajm8gINfI1oVAe3UK59416dT4TqDX1pj4MBJ5TYwir6x3JgBw1-avYHSnmvQDdRMbxmC2sNf3ggIRTbyQBdI1RjvHyeDYCsistnTXF_iKF1nutYeQ2BZ4P5d5muZTG7C2PXVFgNg-18io9dCiSjzJXx93KPDYRiuIwtsGGsp51rojlpFw2Fp_dUkjXl4CAblk58DvwNhobwQ27bnBP8F2-Pcs88DYcvKn4r6dm3Vi7ILttxDQ2IgZ2X44ClgjoWh-vRf6ANis8Z7uL16vO8q0P5C21eDYuc4v_KaZqN-p9YWEeEZQ2OpkbRL7n5NieVJExHM6ANkAlLZhVf2T-1906TAIHKDZFm_xMCa1jJfpBqZB2agw2TjfbK6wMtJeHiZaipSuUNlM_CSH0HVXtfMj9yfzjzDZZnltZQ9lvc4JhXye5AwA2X1f9Dhk8VURTvVdfEUlU', - 'ext': { - 'linkType': 2 - } - } - }, - 'userIdAsEids': [ - { - 'source': 'id5-sync.com', - 'uids': [ - { - 'id': 'ID5*hQ5WobYI9Od4u52qpaXVKHhxUa4DsOWRAlvaFajm8gINfI1oVAe3UK59416dT4TqDX1pj4MBJ5TYwir6x3JgBw1-avYHSnmvQDdRMbxmC2sNf3ggIRTbyQBdI1RjvHyeDYCsistnTXF_iKF1nutYeQ2BZ4P5d5muZTG7C2PXVFgNg-18io9dCiSjzJXx93KPDYRiuIwtsGGsp51rojlpFw2Fp_dUkjXl4CAblk58DvwNhobwQ27bnBP8F2-Pcs88DYcvKn4r6dm3Vi7ILttxDQ2IgZ2X44ClgjoWh-vRf6ANis8Z7uL16vO8q0P5C21eDYuc4v_KaZqN-p9YWEeEZQ2OpkbRL7n5NieVJExHM6ANkAlLZhVf2T-1906TAIHKDZFm_xMCa1jJfpBqZB2agw2TjfbK6wMtJeHiZaipSuUNlM_CSH0HVXtfMj9yfzjzDZZnltZQ9lvc4JhXye5AwA2X1f9Dhk8VURTvVdfEUlU', + 'user': { + 'ext': { + 'consent': 'CPgocUAPgocUAAKAsAENCkCsAP_AAH_AAAqIJDtd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7994JEAEmGrcQBdmWODNoGEUCIEYVhIVQKACCgGFogMAHBwU7KwCfWECABAKAIwIgQ4AowIBAAAJAEhEAEgRYIAAARAIAAQAIhEIAGBgEFgBYGAQAAgGgYohQACBIQZEBEUpgQFQJBAa2VCCUF0hphAHWWAFBIjYqABEEgIrAAEBYOAYIkBKxYIEmKN8gBGCFAKJUK1EAAAA.YAAAAAAAAAAA', + 'ConsentedProvidersSettings': {'consented_providers': '1~39.43.46.55.61.70.83.89.93.108.117.122.124.131.135.136.143.144.147.149.159.162.167.171.192.196.202.211.218.228.230.239.241.259.266.272.286.291.311.317.322.323.326.327.338.367.371.385.389.394.397.407.413.415.424.430.436.445.449.453.482.486.491.494.495.501.503.505.522.523.540.550.559.560.568.574.576.584.587.591.733.737.745.787.802.803.817.820.821.829.839.864.867.874.899.904.922.931.938.979.981.985.1003.1024.1027.1031.1033.1040.1046.1051.1053.1067.1085.1092.1095.1097.1099.1107.1127.1135.1143.1149.1152.1162.1166.1186.1188.1201.1205.1211.1215.1226.1227.1230.1252.1268.1270.1276.1284.1286.1290.1301.1307.1312.1345.1356.1364.1365.1375.1403.1415.1416.1419.1440.1442.1449.1455.1456.1465.1495.1512.1516.1525.1540.1548.1555.1558.1564.1570.1577.1579.1583.1584.1591.1603.1616.1638.1651.1653.1665.1667.1677.1678.1682.1697.1699.1703.1712.1716.1721.1725.1732.1745.1750.1765.1769.1782.1786.1800.1808.1810.1825.1827.1832.1838.1840.1842.1843.1845.1859.1866.1870.1878.1880.1889.1899.1917.1929.1942.1944.1962.1963.1964.1967.1968.1969.1978.2003.2007.2008.2027.2035.2039.2044.2047.2052.2056.2064.2068.2070.2072.2074.2088.2090.2103.2107.2109.2115.2124.2130.2133.2137.2140.2145.2147.2150.2156.2166.2177.2183.2186.2202.2205.2216.2219.2220.2222.2225.2234.2253.2264.2279.2282.2292.2299.2305.2309.2312.2316.2322.2325.2328.2331.2334.2335.2336.2337.2343.2354.2357.2358.2359.2370.2376.2377.2387.2392.2394.2400.2403.2405.2407.2411.2414.2416.2418.2425.2440.2447.2459.2461.2462.2465.2468.2472.2477.2481.2484.2486.2488.2493.2496.2497.2498.2499.2501.2510.2511.2517.2526.2527.2532.2534.2535.2542.2552.2563.2564.2567.2568.2569.2571.2572.2575.2577.2583.2584.2596.2601.2604.2605.2608.2609.2610.2612.2614.2621.2628.2629.2633.2634.2636.2642.2643.2645.2646.2647.2650.2651.2652.2656.2657.2658.2660.2661.2669.2670.2677.2681.2684.2686.2687.2690.2695.2698.2707.2713.2714.2729.2739.2767.2768.2770.2772.2784.2787.2791.2792.2798.2801.2805.2812.2813.2816.2817.2818.2821.2822.2827.2830.2831.2834.2838.2839.2840.2844.2846.2847.2849.2850.2852.2854.2856.2860.2862.2863.2865.2867.2869.2873.2874.2875.2876.2878.2880.2881.2882.2883.2884.2886.2887.2888.2889.2891.2893.2894.2895.2897.2898.2900.2901.2908.2909.2911.2912.2913.2914.2916.2917.2918.2919.2920.2922.2923.2924.2927.2929.2930.2931.2939.2940.2941.2947.2949.2950.2956.2961.2962.2963.2964.2965.2966.2968.2970.2973.2974.2975.2979.2980.2981.2983.2985.2986.2987.2991.2994.2995.2997.2999.3000.3002.3003.3005.3008.3009.3010.3012.3016.3017.3018.3019.3024.3025.3028.3034.3037.3038.3043.3045.3048.3052.3053.3055.3058.3059.3063.3065.3066.3068.3070.3072.3073.3074.3075.3076.3077.3078.3089.3090.3093.3094.3095.3097.3099.3104.3106.3109.3112.3117.3118.3119.3120.3124.3126.3127.3128.3130.3135.3136.3145.3149.3150.3151.3154.3155.3162.3163.3167.3172.3173.3180.3182.3183.3184.3185.3187.3188.3189.3190.3194.3196.3197.3209.3210.3211.3214.3215.3217.3219.3222.3223.3225.3226.3227.3228.3230.3231.3232.3234.3235.3236.3237.3238.3240.3241.3244.3245.3250.3251.3253.3257.3260.3268.3270.3272.3281.3288.3290.3292.3293.3295.3296.3300.3306.3307.3308.3314.3315.3316.3318.3324.3327.3328.3330'}, + 'eids': [{'source': 'id5-sync.com', + 'uids': [{'id': 'ID5*tdrSpYbccONIbxmulXFRLEil1aozZGGVMo9eEZgydgYoYFZQRYoae3wJyY0YtmXGKGJ7uXIQByQ6f7uzcpy9Oyhj1jGRzCf0BCoI4VkkKZIoZBubolUKUXXxOIdQOz7ZKGV0E3sqi9Zut0BbOuoJAihpLbgfNgDJ0xRmQw04rDooaxn7_TIPzEX5_L5ohNkUKG01Gnh2djvcrcPigKlk7ChwnauCwHIetHYI32yYAnAocYyqoM9XkoVOHtyOTC_UKHIR0qVBVIzJ1Nn_g7kLqyhzfosadKVvf7RQCsE6QrYodtpOJKg7i72-tnMXkzgmKHjh98aEDfTQrZOkKebmAyh6GlOHtYn_sZBFjJwtWp4oe9j2QTNbzK3G0jp1PlJqKHxiu4LawFEKJ3yi5-NFUyh-YkEalJUWyl1cDlWo5NQogAy2HM8N_w0qrVQgNbrTKIHK3KzTXztH7WzBgYrk8g', 'atype': 1, - 'ext': { - 'linkType': 2 - } - } - ] - } - ], - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'adUnitCode': 'banner-div', - 'transactionId': '9ad89d90-eb73-41b9-bf5f-7a8e2eecff27', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '4d9e29504f8af6', - 'bidderRequestId': '3423b6bd1a922c', - 'auctionId': '05e0a3a1-9f57-41f6-bbcb-2ba9c9e3d2d5', - 'src': 'client', - 'bidRequestsCount': 1, - 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 - }]; - - const DISPLAY_BID_RESPONSE = {'body': { - 'responses': [ - { - 'bidId': '4d9e29504f8af6', - 'cpm': 0.437245, - 'width': 300, - 'height': 250, - 'creativeId': '98493581', - 'currency': 'EUR', - 'netRevenue': true, - 'type': 'banner', - 'ttl': 360, - 'uuid': 'ce6d1ee3-2a05-4d7c-b97a-9e62097798ec', - 'bidder': 'appnexus', - 'consent': 1, - 'tagId': 'luvxjvgn' - } - ], - }}; + 'ext': {'linkType': 2}}]}, + {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}]}}, + 'ext': { + 'source': 'prebid.js', + 'version': '7.20.0-pre', + 'pageViewId': '5b970aba-51e9-4e0a-8299-f3f5618c695e' + }} const VIDEO_BID_REQUEST = [ { @@ -101,26 +70,6 @@ describe('Nexx360 bid adapter tests', function () { } ] - const VIDEO_BID_RESPONSE = {'body': { - 'responses': [ - { - 'bidId': '2c129e8e01859a', - 'type': 'video', - 'uuid': 'b8e7b2f0-c378-479f-aa4f-4f55d5d7d1d5', - 'cpm': 4.5421, - 'width': 1, - 'height': 1, - 'creativeId': '97517771', - 'currency': 'EUR', - 'netRevenue': true, - 'ttl': 360, - 'bidder': 'appnexus', - 'consent': 1, - 'tagId': 'yqsc1tfj' - } - ] - }}; - const DEFAULT_OPTIONS = { gdprConsent: { gdprApplies: true, @@ -147,159 +96,426 @@ describe('Nexx360 bid adapter tests', function () { }, }; - it('We verify isBidRequestValid with uncorrect bidfloorCurrency', function() { - const bid = { params: { - 'account': '1067', - 'tagId': 'luvxjvgn', - 'bidfloorCurrency': 'AAA', - }}; - expect(spec.isBidRequestValid(bid)).to.be.equal(false); - }); + describe('isBidRequestValid()', function() { + let bannerBid; + beforeEach(function () { + bannerBid = { + 'bidder': 'nexx360', + 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 600]]}}, + 'adUnitCode': 'div-1', + 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4906582fc87d0c', + 'bidderRequestId': '332fda16002dbe', + 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', + } + }); + it('We verify isBidRequestValid with uncorrect tagid', function() { + bannerBid.params = { 'tagid': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); - it('We verify isBidRequestValid with uncorrect bidfloor', function() { - const bid = { params: { - 'account': '1067', - 'tagId': 'luvxjvgn', - 'bidfloorCurrency': 'EUR', - 'bidfloor': 'EUR', - }}; - expect(spec.isBidRequestValid(bid)).to.be.equal(false); + it('We verify isBidRequestValid with correct tagId', function() { + bannerBid.params = { 'tagId': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); }); - it('We verify isBidRequestValid with uncorrect keywords', function() { - const bid = { params: { - 'account': '1067', - 'tagId': 'luvxjvgn', - 'bidfloorCurrency': 'EUR', - 'bidfloor': 0.8, - 'keywords': 'test', - }}; - expect(spec.isBidRequestValid(bid)).to.be.equal(false); + describe('when request is for a multiformat ad', function () { + describe('and request config uses mediaTypes video and banner', () => { + const multiformatBid = { + 'bidder': 'nexx360', + 'params': {'tagId': 'luvxjvgn'}, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + }, + 'video': { + 'playerSize': [300, 250] + } + }, + 'adUnitCode': 'div-1', + 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '4906582fc87d0c', + 'bidderRequestId': '332fda16002dbe', + 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', + } + it('should return true multisize when required params found', function () { + expect(spec.isBidRequestValid(multiformatBid)).to.equal(true); + }); + }); }); - it('Verify banner build request', function () { - const request = spec.buildRequests(DISPLAY_BID_REQUEST, DEFAULT_OPTIONS); - expect(request).to.have.property('url').and.to.equal('https://fast.nexx360.io/prebid'); - expect(request).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request.data); - expect(requestContent.userEids.length).to.be.eql(1); - expect(requestContent.userEids[0]).to.have.property('source').and.to.equal('id5-sync.com'); - expect(requestContent.userEids[0]).to.have.property('uids'); - expect(requestContent.userEids[0].uids[0]).to.have.property('id').and.to.equal('ID5*hQ5WobYI9Od4u52qpaXVKHhxUa4DsOWRAlvaFajm8gINfI1oVAe3UK59416dT4TqDX1pj4MBJ5TYwir6x3JgBw1-avYHSnmvQDdRMbxmC2sNf3ggIRTbyQBdI1RjvHyeDYCsistnTXF_iKF1nutYeQ2BZ4P5d5muZTG7C2PXVFgNg-18io9dCiSjzJXx93KPDYRiuIwtsGGsp51rojlpFw2Fp_dUkjXl4CAblk58DvwNhobwQ27bnBP8F2-Pcs88DYcvKn4r6dm3Vi7ILttxDQ2IgZ2X44ClgjoWh-vRf6ANis8Z7uL16vO8q0P5C21eDYuc4v_KaZqN-p9YWEeEZQ2OpkbRL7n5NieVJExHM6ANkAlLZhVf2T-1906TAIHKDZFm_xMCa1jJfpBqZB2agw2TjfbK6wMtJeHiZaipSuUNlM_CSH0HVXtfMj9yfzjzDZZnltZQ9lvc4JhXye5AwA2X1f9Dhk8VURTvVdfEUlU'); - expect(requestContent.adUnits[0]).to.have.property('account').and.to.equal('1067'); - expect(requestContent.adUnits[0]).to.have.property('tagId').and.to.equal('luvxjvgn'); - expect(requestContent.adUnits[0]).to.have.property('label').and.to.equal('banner-div'); - expect(requestContent.adUnits[0]).to.have.property('bidId').and.to.equal('4d9e29504f8af6'); - expect(requestContent.adUnits[0]).to.have.property('auctionId').and.to.equal('05e0a3a1-9f57-41f6-bbcb-2ba9c9e3d2d5'); - expect(requestContent.adUnits[0]).to.have.property('mediatypes').exist; - expect(requestContent.adUnits[0].mediatypes).to.have.property('banner').exist; - expect(requestContent.adUnits[0]).to.have.property('bidfloor').and.to.equal(0); - expect(requestContent.adUnits[0]).to.have.property('bidfloorCurrency').and.to.equal('USD'); - expect(requestContent.adUnits[0]).to.have.property('keywords'); - expect(requestContent.adUnits[0].keywords.length).to.be.eql(0); + describe('when request is for a video ad', function () { + describe('and request config uses mediaTypes video and banner', () => { + const videoBid = { + 'bidder': 'nexx360', + 'params': {'tagId': 'luvxjvgn'}, + 'mediaTypes': { + 'video': { + 'playerSize': [300, 250] + } + }, + 'adUnitCode': 'div-1', + 'transactionId': '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + 'bidId': '4906582fc87d0c', + 'bidderRequestId': '332fda16002dbe', + 'auctionId': '98932591-c822-42e3-850e-4b3cf748d063', + } + it('should return true multisize when required params found', function () { + expect(spec.isBidRequestValid(videoBid)).to.equal(true); + }); + }); }); - it('We add bidfloor and keywords', function() { - const DISPLAY_BID_REQUEST_2 = [ ...DISPLAY_BID_REQUEST ]; - DISPLAY_BID_REQUEST_2[0].params.keywords = { interest: 'cars' }; - DISPLAY_BID_REQUEST_2[0].params.bidfloor = 2.1; - const request = spec.buildRequests(DISPLAY_BID_REQUEST, DEFAULT_OPTIONS); - const requestContent = JSON.parse(request.data); - expect(requestContent.adUnits[0]).to.have.property('bidfloor').and.to.equal(2.1); - expect(requestContent.adUnits[0]).to.have.property('bidfloorCurrency').and.to.equal('USD'); - expect(requestContent.adUnits[0]).to.have.property('keywords'); - expect(requestContent.adUnits[0].keywords.length).to.be.eql(1); - expect(requestContent.adUnits[0].keywords[0].key).to.be.eql('interest'); - expect(requestContent.adUnits[0].keywords[0].value[0]).to.be.eql('cars'); - }); + describe('buildRequests()', function() { + describe('We test with a multiple display bids', function() { + const sampleBids = [ + { + bidder: 'nexx360', + params: { + tagId: 'luvxjvgn' + }, + adUnitCode: 'div-1', + transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', + sizes: [[300, 250], [300, 600]], + bidId: '44a2706ac3574', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + userIdAsEids: [ + { + source: 'id5-sync.com', + uids: [ + { + id: 'ID5*xe3R0Pbrc5Y4WBrb5UZSWTiS1t9DU2LgQrhdZOgFdXMoglhqmjs_SfBbyHfSYGZKKIT4Gf-XOQ_anA3iqi0hJSiFyD3aICGHDJFxNS8LO84ohwTQ0EiwOexZAbBlH0chKIhbvdGBfuouNuVF_YHCoyiLQJDp3WQiH96lE9MH2T0ojRqoyR623gxAWlBCBPh7KI4bYtZlet3Vtr-gH5_xqCiSEd7aYV37wHxUTSN38Isok_0qDCHg4pKXCcVM2h6FKJSGmvw-xPm9HkfkIcbh1CiVVG4nREP142XrBecdzhQomNlcalmwdzGHsuHPjTP-KJraa15yvvZDceq-f_YfECicDllYBLEsg24oPRM-ibMonWtT9qOm5dSfWS5G_r09KJ4HMB6REICq1wleDD1mwSigXkM_nxIKa4TxRaRqEekoooWRwuKA5-euHN3xxNfIKKP19EtGhuNTs0YdCSe8_w', + atype: 1, + ext: { + linkType: 2 + } + } + ] + }, + { + source: 'domain.com', + uids: [ + { + id: 'value read from cookie or local storage', + atype: 1, + ext: { + stype: 'ppuid' + } + } + ] + } + ], + }, + { + bidder: 'nexx360', + params: { + tagId: 'luvxjvgn', + allBids: true, + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 250]] + } + }, + adUnitCode: 'div-2', + transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', + sizes: [[728, 90], [970, 250]], + bidId: '5ba94555219a03', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + } + ] + const bidderRequest = { + bidderCode: 'nexx360', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + bidderRequestId: '359bf8a3c06b2e', + refererInfo: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + topmostLocation: 'https://test.nexx360.io/adapter/index.html', + location: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null, + page: 'https://test.nexx360.io/adapter/index.html', + domain: 'test.nexx360.io', + ref: null, + legacy: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + referer: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null + }, + }, + gdprConsent: { + gdprApplies: true, + consentString: 'CPhdLUAPhdLUAAKAsAENCmCsAP_AAE7AAAqIJFNd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7997BIiAaADgAJYBnwEeAJXAXmAwQBj4DtgHcgPBAeKBIgAA.YAAAAAAAAAAA', + } + }; + it('We perform a test with 2 display adunits', function() { + const displayBids = [...sampleBids]; + displayBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + } + }; + const request = spec.buildRequests(displayBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.id).to.be.eql('2e684815-b44e-4e04-b812-56da54adbe74'); + expect(requestContent.cur[0]).to.be.eql('USD'); + expect(requestContent.imp.length).to.be.eql(2); + expect(requestContent.imp[0].id).to.be.eql('44a2706ac3574'); + expect(requestContent.imp[0].tagid).to.be.eql('div-1'); + expect(requestContent.imp[0].ext.divId).to.be.eql('div-1'); + expect(requestContent.imp[0].ext.nexx360.tagId).to.be.eql('luvxjvgn'); + expect(requestContent.imp[0].ext.nexx360.allBids).to.be.eql(false); + expect(requestContent.imp[0].banner.format.length).to.be.eql(2); + expect(requestContent.imp[0].banner.format[0].w).to.be.eql(300); + expect(requestContent.imp[0].banner.format[0].h).to.be.eql(250); + expect(requestContent.imp[1].ext.nexx360.allBids).to.be.eql(true); + expect(requestContent.regs.ext.gdpr).to.be.eql(1); + expect(requestContent.user.ext.consent).to.be.eql(bidderRequest.gdprConsent.consentString); + expect(requestContent.user.ext.eids.length).to.be.eql(2); + }); - it('Verify banner parse response', function () { - const request = spec.buildRequests(DISPLAY_BID_REQUEST, DEFAULT_OPTIONS); - const response = spec.interpretResponse(DISPLAY_BID_RESPONSE, request); - expect(response).to.have.lengthOf(1); - const bid = response[0]; - expect(bid.cpm).to.equal(0.437245); - expect(bid.adUrl).to.equal('https://fast.nexx360.io/cache?uuid=ce6d1ee3-2a05-4d7c-b97a-9e62097798ec'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.creativeId).to.equal('98493581'); - expect(bid.currency).to.equal('EUR'); - expect(bid.netRevenue).to.equal(true); - expect(bid.ttl).to.equal(360); - expect(bid.requestId).to.equal('4d9e29504f8af6'); - expect(bid.nexx360).to.exist; - expect(bid.nexx360.ssp).to.equal('appnexus'); - }); + it('We perform a test with a multiformat adunit', function() { + const multiformatBids = [...sampleBids]; + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const requestContent = request.data; + expect(requestContent.imp[0].video.context).to.be.eql('outstream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); - it('Verify video build request', function () { - const request = spec.buildRequests(VIDEO_BID_REQUEST, DEFAULT_OPTIONS); - expect(request).to.have.property('url').and.to.equal('https://fast.nexx360.io/prebid'); - expect(request).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request.data); - expect(requestContent.adUnits[0]).to.have.property('account').and.to.equal('1067'); - expect(requestContent.adUnits[0]).to.have.property('tagId').and.to.equal('yqsc1tfj'); - expect(requestContent.adUnits[0]).to.have.property('label').and.to.equal('video1'); - expect(requestContent.adUnits[0]).to.have.property('bidId').and.to.equal('22f90541e576a3'); - expect(requestContent.adUnits[0]).to.have.property('auctionId').and.to.equal('ed21b528-bcab-47e2-8605-ec9b71000c89'); - expect(requestContent.adUnits[0]).to.have.property('mediatypes').exist; - expect(requestContent.adUnits[0].mediatypes).to.have.property('video').exist; + it('We perform a test with a video adunit', function() { + const videoBids = [sampleBids[0]]; + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }) + }); }); - it('Verify video parse response', function () { - const request = spec.buildRequests(VIDEO_BID_REQUEST, DEFAULT_OPTIONS); - const response = spec.interpretResponse(VIDEO_BID_RESPONSE, request); - expect(response).to.have.lengthOf(1); - const bid = response[0]; - expect(bid.cpm).to.equal(4.5421); - expect(bid.vastUrl).to.equal('https://fast.nexx360.io/cache?uuid=b8e7b2f0-c378-479f-aa4f-4f55d5d7d1d5'); - expect(bid.vastImpUrl).to.equal('https://fast.nexx360.io/track-imp?type=prebid&mediatype=video&ssp=appnexus&tag_id=yqsc1tfj&consent=1&price=4.5421'); - expect(bid.width).to.equal(1); - expect(bid.height).to.equal(1); - expect(bid.creativeId).to.equal('97517771'); - expect(bid.currency).to.equal('EUR'); - expect(bid.netRevenue).to.equal(true); - expect(bid.ttl).to.equal(360); - expect(bid.requestId).to.equal('2c129e8e01859a'); - expect(bid.nexx360).to.exist; - expect(bid.nexx360.ssp).to.equal('appnexus'); + describe('interpretResponse()', function() { + it('banner responses', function() { + const response = { + body: { + 'id': 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'id': '4427551302944024629', + 'impid': '226175918ebeda', + 'price': 1.5, + 'type': 'banner', + 'adomain': [ + 'http://prebid.org' + ], + 'crid': '98493581', + 'ssp': 'appnexus', + 'h': 600, + 'w': 300, + 'cat': [ + 'IAB3-1' + ], + 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', + 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + 'ext': { + 'dsp_id': 'ssp1', + 'buyer_id': 'foo', + 'brand_id': 'bar' + } + } + ], + 'seat': 'appnexus' + } + ], + 'cookies': [] + } + }; + const output = spec.interpretResponse(response); + expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); + expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].currency).to.be.eql(response.body.cur); + expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + expect(output[0].meta.networkId).to.be.eql(response.body.seatbid[0].bid[0].ext.dsp_id); + expect(output[0].meta.advertiserId).to.be.eql(response.body.seatbid[0].bid[0].ext.buyer_id); + expect(output[0].meta.brandId).to.be.eql(response.body.seatbid[0].bid[0].ext.brand_id); + }); + it('video responses', function() { + const response = { + body: { + 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'id': '294478680080716675', + 'impid': '2c835a6039e65f', + 'price': 5, + 'type': 'instream', + 'adomain': [ + '' + ], + 'crid': '97517771', + 'ssp': 'appnexus', + 'h': 1, + 'w': 1, + 'vastXml': '\n \n \n prebid.org wrapper\n \n \n \n \n \n ' + } + ], + 'seat': 'appnexus' + } + ], + 'cookies': [] + } + }; + const output = spec.interpretResponse(response); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); + expect(output[0].mediaType).to.be.eql('video'); + expect(output[0].currency).to.be.eql(response.body.cur); + expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + }); }); - it('Verifies bidder code', function () { - expect(spec.code).to.equal('nexx360'); + describe('interpretResponse()', function() { + it('banner responses', function() { + const response = { + body: { + 'id': 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'id': '4427551302944024629', + 'impid': '226175918ebeda', + 'price': 1.5, + 'type': 'banner', + 'adomain': [ + 'http://prebid.org' + ], + 'crid': '98493581', + 'ssp': 'appnexus', + 'h': 600, + 'w': 300, + 'cat': [ + 'IAB3-1' + ], + 'creativeuuid': 'fdddcebc-1edf-489d-880d-1418d8bdc493', + 'adUrl': 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493' + } + ], + 'seat': 'appnexus' + } + ], + 'cookies': [] + } + }; + const output = spec.interpretResponse(response); + expect(output[0].adUrl).to.be.eql(response.body.seatbid[0].bid[0].adUrl); + expect(output[0].mediaType).to.be.eql(response.body.seatbid[0].bid[0].type); + expect(output[0].currency).to.be.eql(response.body.cur); + expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + }); + it('video responses', function() { + const response = { + body: { + 'id': '33894759-0ea2-41f1-84b3-75132eefedb6', + 'cur': 'USD', + 'seatbid': [ + { + 'bid': [ + { + 'id': '294478680080716675', + 'impid': '2c835a6039e65f', + 'price': 5, + 'type': 'instream', + 'adomain': [ + '' + ], + 'crid': '97517771', + 'ssp': 'appnexus', + 'h': 1, + 'w': 1, + 'vastXml': '\n \n \n prebid.org wrapper\n \n \n \n \n \n ' + } + ], + 'seat': 'appnexus' + } + ], + 'cookies': [] + } + }; + const output = spec.interpretResponse(response); + expect(output[0].vastXml).to.be.eql(response.body.seatbid[0].bid[0].vastXml); + expect(output[0].mediaType).to.be.eql('video'); + expect(output[0].currency).to.be.eql(response.body.cur); + expect(output[0].cpm).to.be.eql(response.body.seatbid[0].bid[0].price); + }); }); - it('Verifies bidder aliases', function () { - expect(spec.aliases).to.have.lengthOf(1); - expect(spec.aliases[0]).to.equal('revenuemaker'); - }); - it('Verifies if bid request valid', function () { - expect(spec.isBidRequestValid(DISPLAY_BID_REQUEST[0])).to.equal(true); - }); - it('Verifies bid won', function () { - const request = spec.buildRequests(DISPLAY_BID_REQUEST, DEFAULT_OPTIONS); - const response = spec.interpretResponse(DISPLAY_BID_RESPONSE, request); - const won = spec.onBidWon(response[0]); - expect(won).to.equal(true); - }); - it('Verifies user sync without cookie in bid response', function () { - var syncs = spec.getUserSyncs({}, [DISPLAY_BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); - }); - it('Verifies user sync with cookies in bid response', function () { - DISPLAY_BID_RESPONSE.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; - var syncs = spec.getUserSyncs({}, [DISPLAY_BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0]).to.have.property('type').and.to.equal('image'); - expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); - }); - it('Verifies user sync with no bid response', function() { - var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); - }); - it('Verifies user sync with no bid body response', function() { - var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); - var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.lengthOf(0); + describe('getUserSyncs()', function() { + const response = { body: { cookies: [] } }; + it('Verifies user sync without cookie in bid response', function () { + var syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + it('Verifies user sync with cookies in bid response', function () { + response.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; + var syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0]).to.have.property('type').and.to.equal('image'); + expect(syncs[0]).to.have.property('url').and.to.equal('http://www.cookie.sync.org/'); + }); + it('Verifies user sync with no bid response', function() { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); + it('Verifies user sync with no bid body response', function() { + var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.have.lengthOf(0); + }); }); }); diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index 8871d2d0886..f7cdcc0d11a 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -119,26 +119,30 @@ describe('OguryBidAdapter', function () { }; }); - it('should return syncs array with two elements of type image', () => { + it('should return syncs array with three elements of type image', () => { const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); expect(userSyncs[0].url).to.contain('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch'); expect(userSyncs[1].type).to.equal('image'); expect(userSyncs[1].url).to.contain('https://ms-cookie-sync.presage.io/ttd/init-sync'); + expect(userSyncs[2].type).to.equal('image'); + expect(userSyncs[2].url).to.contain('https://ms-cookie-sync.presage.io/xandr/init-sync'); }); it('should set the source as query param', () => { const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); expect(userSyncs[0].url).to.contain('source=prebid'); expect(userSyncs[1].url).to.contain('source=prebid'); + expect(userSyncs[2].url).to.contain('source=prebid'); }); it('should set the tcString as query param', () => { const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); expect(userSyncs[0].url).to.contain(`iab_string=${gdprConsent.consentString}`); expect(userSyncs[1].url).to.contain(`iab_string=${gdprConsent.consentString}`); + expect(userSyncs[2].url).to.contain(`iab_string=${gdprConsent.consentString}`); }); it('should return an empty array when pixel is disable', () => { @@ -146,94 +150,110 @@ describe('OguryBidAdapter', function () { expect(spec.getUserSyncs(syncOptions, [], gdprConsent)).to.have.lengthOf(0); }); - it('should return syncs array with two elements of type image when consentString is undefined', () => { + it('should return syncs array with three elements of type image when consentString is undefined', () => { gdprConsent = { gdprApplies: true, consentString: undefined }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') expect(userSyncs[1].type).to.equal('image'); expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(userSyncs[2].type).to.equal('image'); + expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') }); - it('should return syncs array with two elements of type image when consentString is null', () => { + it('should return syncs array with three elements of type image when consentString is null', () => { gdprConsent = { gdprApplies: true, consentString: null }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') expect(userSyncs[1].type).to.equal('image'); expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(userSyncs[2].type).to.equal('image'); + expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') }); - it('should return syncs array with two elements of type image when gdprConsent is undefined', () => { + it('should return syncs array with three elements of type image when gdprConsent is undefined', () => { gdprConsent = undefined; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') expect(userSyncs[1].type).to.equal('image'); expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(userSyncs[2].type).to.equal('image'); + expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') }); - it('should return syncs array with two elements of type image when gdprConsent is null', () => { + it('should return syncs array with three elements of type image when gdprConsent is null', () => { gdprConsent = null; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') expect(userSyncs[1].type).to.equal('image'); expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(userSyncs[2].type).to.equal('image'); + expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') }); - it('should return syncs array with two elements of type image when gdprConsent is null and gdprApplies is false', () => { + it('should return syncs array with three elements of type image when gdprConsent is null and gdprApplies is false', () => { gdprConsent = { gdprApplies: false, consentString: null }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') expect(userSyncs[1].type).to.equal('image'); expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(userSyncs[2].type).to.equal('image'); + expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') }); - it('should return syncs array with two elements of type image when gdprConsent is empty string and gdprApplies is false', () => { + it('should return syncs array with three elements of type image when gdprConsent is empty string and gdprApplies is false', () => { gdprConsent = { gdprApplies: false, consentString: '' }; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs).to.have.lengthOf(2); + expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') expect(userSyncs[1].type).to.equal('image'); expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(userSyncs[2].type).to.equal('image'); + expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') }); }); describe('buildRequests', function () { const stubbedWidth = 200 const stubbedHeight = 600 + const stubbedCurrentTime = 1234567890 const stubbedWidthMethod = sinon.stub(window.top.document.documentElement, 'clientWidth').get(function() { return stubbedWidth; }); const stubbedHeightMethod = sinon.stub(window.top.document.documentElement, 'clientHeight').get(function() { return stubbedHeight; }); + const stubbedCurrentTimeMethod = sinon.stub(document.timeline, 'currentTime').get(function() { + return stubbedCurrentTime; + }); const defaultTimeout = 1000; const expectedRequestObject = { @@ -250,7 +270,10 @@ describe('OguryBidAdapter', function () { h: 250 }] }, - ext: bidRequests[0].params + ext: { + ...bidRequests[0].params, + timeSpentOnPage: stubbedCurrentTime + } }, { id: bidRequests[1].bidId, tagid: bidRequests[1].params.adUnitId, @@ -260,7 +283,10 @@ describe('OguryBidAdapter', function () { h: 500 }] }, - ext: bidRequests[1].params + ext: { + ...bidRequests[1].params, + timeSpentOnPage: stubbedCurrentTime + } }], regs: { ext: { @@ -279,7 +305,7 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.2.13' + adapterversion: '1.4.0' }, device: { w: stubbedWidth, @@ -290,6 +316,7 @@ describe('OguryBidAdapter', function () { after(function() { stubbedWidthMethod.restore(); stubbedHeightMethod.restore(); + stubbedCurrentTimeMethod.restore(); }); it('sends bid request to ENDPOINT via POST', function () { @@ -300,6 +327,17 @@ describe('OguryBidAdapter', function () { expect(request.method).to.equal('POST'); }); + it('timeSpentOnpage should be 0 if timeline is undefined', function () { + const stubbedTimelineMethod = sinon.stub(document, 'timeline').get(function() { + return undefined; + }); + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data.imp[0].ext.timeSpentOnPage).to.equal(0); + stubbedTimelineMethod.restore(); + }); + it('bid request object should be conform', function () { const validBidRequests = utils.deepClone(bidRequests) @@ -659,7 +697,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.2.13', + adapterVersion: '1.4.0', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -676,7 +714,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.2.13', + adapterVersion: '1.4.0', prebidVersion: '$prebid.version$' }] diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 2dc0a43bbb0..5bd65cf0fd5 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -1,8 +1,8 @@ import { spec, isValid, hasTypeVideo, isSchainValid } from 'modules/onetagBidAdapter.js'; import { expect } from 'chai'; -import {find} from 'src/polyfill.js'; +import { find } from 'src/polyfill.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import {INSTREAM, OUTSTREAM} from 'src/video.js'; +import { INSTREAM, OUTSTREAM } from 'src/video.js'; describe('onetag', function () { function createBid() { @@ -122,14 +122,14 @@ describe('onetag', function () { it('Should return true when correct multi format bid is passed', function () { expect(spec.isBidRequestValid(createMultiFormatBid())).to.be.true; }); - it('Should split multi format bid into two single format bid with same bidId', function() { - const bids = JSON.parse(spec.buildRequests([ createMultiFormatBid() ]).data).bids; + it('Should split multi format bid into two single format bid with same bidId', function () { + const bids = JSON.parse(spec.buildRequests([createMultiFormatBid()]).data).bids; expect(bids.length).to.equal(2); expect(bids[0].bidId).to.equal(bids[1].bidId); }); - it('Should retrieve correct request bid when extracting video request data', function() { + it('Should retrieve correct request bid when extracting video request data', function () { const requestBid = createMultiFormatBid(); - const multiFormatRequest = spec.buildRequests([ requestBid ]); + const multiFormatRequest = spec.buildRequests([requestBid]); const serverResponse = { body: { bids: [ @@ -173,24 +173,30 @@ describe('onetag', function () { const data = JSON.parse(d); it('Should contain all keys', function () { expect(data).to.be.an('object'); - expect(data).to.include.all.keys('location', 'referrer', 'masked', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'timing', 'version'); - expect(data.location).to.be.a('string'); - expect(data.masked).to.be.oneOf([0, 1, 2]); + expect(data).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'networkConnectionType', 'networkEffectiveConnectionType', 'timing', 'version'); + expect(data.location).to.satisfy(function (value) { + return value === null || typeof value === 'string'; + }); expect(data.referrer).to.satisfy(referrer => referrer === null || typeof referrer === 'string'); + expect(data.stack).to.be.an('array'); + expect(data.numIframes).to.be.a('number'); expect(data.sHeight).to.be.a('number'); expect(data.sWidth).to.be.a('number'); expect(data.wWidth).to.be.a('number'); expect(data.wHeight).to.be.a('number'); expect(data.oHeight).to.be.a('number'); expect(data.oWidth).to.be.a('number'); - expect(data.ancestorOrigin).to.satisfy(function (value) { - return value === null || typeof value === 'string'; - }); expect(data.aWidth).to.be.a('number'); expect(data.aHeight).to.be.a('number'); expect(data.sLeft).to.be.a('number'); expect(data.sTop).to.be.a('number'); expect(data.hLength).to.be.a('number'); + expect(data.networkConnectionType).to.satisfy(function (value) { + return value === null || typeof value === 'string' + }); + expect(data.networkEffectiveConnectionType).to.satisfy(function (value) { + return value === null || typeof value === 'string' + }); expect(data.bids).to.be.an('array'); expect(data.version).to.have.all.keys('prebid', 'adapter'); const bids = data['bids']; @@ -231,14 +237,14 @@ describe('onetag', function () { expect(bid.pubId).to.be.a('string'); } }); - } catch (e) {} + } catch (e) { } it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); let dataString = serverRequest.data; try { let dataObj = JSON.parse(dataString); expect(dataObj.bids).to.be.an('array').that.is.empty; - } catch (e) {} + } catch (e) { } }); it('should send GDPR consent data', function () { let consentString = 'consentString'; @@ -260,6 +266,27 @@ describe('onetag', function () { expect(payload.gdprConsent.consentString).to.exist.and.to.equal(consentString); expect(payload.gdprConsent.consentRequired).to.exist.and.to.be.true; }); + it('Should send GPP consent data', function () { + let consentString = 'consentString'; + let applicableSections = [1, 2, 3]; + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + gppString: consentString, + applicableSections: applicableSections + } + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload).to.exist; + expect(payload.gppConsent).to.exist; + expect(payload.gppConsent.consentString).to.exist.and.to.equal(consentString); + expect(payload.gppConsent.applicableSections).to.have.same.members(applicableSections); + }); it('Should send us privacy string', function () { let consentString = 'us_foo'; let bidderRequest = { @@ -287,7 +314,7 @@ describe('onetag', function () { let dataItem = interpretedResponse[i]; expect(dataItem).to.include.all.keys('requestId', 'cpm', 'width', 'height', 'ttl', 'creativeId', 'netRevenue', 'currency', 'meta', 'dealId'); if (dataItem.meta.mediaType === VIDEO) { - const {context} = find(requestData.bids, (item) => item.bidId === dataItem.requestId); + const { context } = find(requestData.bids, (item) => item.bidId === dataItem.requestId); if (context === INSTREAM) { expect(dataItem).to.include.all.keys('videoCacheKey', 'vastUrl'); expect(dataItem.vastUrl).to.be.a('string'); @@ -321,7 +348,7 @@ describe('onetag', function () { describe('getUserSyncs', function () { const sync_endpoint = 'https://onetag-sys.com/usync/'; it('Returns an iframe if iframeEnabled is true', function () { - const syncs = spec.getUserSyncs({iframeEnabled: true}); + const syncs = spec.getUserSyncs({ iframeEnabled: true }); expect(syncs).to.be.an('array'); expect(syncs.length).to.equal(1); expect(syncs[0].type).to.equal('iframe'); @@ -369,6 +396,28 @@ describe('onetag', function () { expect(syncs[0].url).to.include(sync_endpoint); expect(syncs[0].url).to.not.match(/(?:[?&](?:gdpr_consent=([^&]*)|gdpr=([^&]*)))+$/); }); + it('Must pass gpp consent string when gppConsent object is available', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: 'foo' + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.match(/(?:[?&](?:gpp_consent=foo([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is null', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, { + gppString: null + }); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); + it('Must pass no gpp params when consentString is empty', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, {}, {}); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include(sync_endpoint); + expect(syncs[0].url).to.not.match(/(?:[?&](?:gpp_consent=([^&]*)))+$/); + }); it('Should send us privacy string', function () { let usConsentString = 'us_foo'; const syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, {}, usConsentString); @@ -383,16 +432,16 @@ describe('onetag', function () { expect(isSchainValid(undefined)).to.be.false; }); it('Should return false when schain is missing nodes key', function () { - const schain = {'otherKey': 'otherValue'}; + const schain = { 'otherKey': 'otherValue' }; expect(isSchainValid(schain)).to.be.false; }); it('Should return false when schain is missing one of the required SupplyChainNode attribute', function () { - const missingAsiNode = {'sid': '00001', 'hp': 1}; - const missingSidNode = {'asi': 'indirectseller.com', 'hp': 1}; - const missingHpNode = {'asi': 'indirectseller.com', 'sid': '00001'}; - expect(isSchainValid({'config': {'nodes': [missingAsiNode]}})).to.be.false; - expect(isSchainValid({'config': {'nodes': [missingSidNode]}})).to.be.false; - expect(isSchainValid({'config': {'nodes': [missingHpNode]}})).to.be.false; + const missingAsiNode = { 'sid': '00001', 'hp': 1 }; + const missingSidNode = { 'asi': 'indirectseller.com', 'hp': 1 }; + const missingHpNode = { 'asi': 'indirectseller.com', 'sid': '00001' }; + expect(isSchainValid({ 'config': { 'nodes': [missingAsiNode] } })).to.be.false; + expect(isSchainValid({ 'config': { 'nodes': [missingSidNode] } })).to.be.false; + expect(isSchainValid({ 'config': { 'nodes': [missingHpNode] } })).to.be.false; }); it('Should return true when schain contains all required attributes', function () { const validSchain = { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index cc1c2d1e607..8fe220aa202 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1729,6 +1729,47 @@ describe('OpenxAdapter', function () { }); }); }); + describe('with user agent client hints', function () { + it('should add json query param sua with BidRequest.device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests([bidRequest], bidderRequestWithUserAgentClientHints); + expect(request[0].data.sua).to.exist; + const payload = JSON.parse(request[0].data.sua); + expect(payload).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests([bidRequest], bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.sua).to.not.exist; + }); + }); }); }) diff --git a/test/spec/modules/openxOrtbBidAdapter_spec.js b/test/spec/modules/openxOrtbBidAdapter_spec.js index 246411b457a..3bb1958c5fe 100644 --- a/test/spec/modules/openxOrtbBidAdapter_spec.js +++ b/test/spec/modules/openxOrtbBidAdapter_spec.js @@ -4,10 +4,26 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from 'src/mediaTypes.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {deepClone} from 'src/utils.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {hook} from '../../../src/hook.js'; const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; describe('OpenxRtbAdapter', function () { + before(() => { + hook.ready(); + }); + const adapter = newBidder(spec); describe('inherited functions', function () { @@ -563,6 +579,47 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + describe('with user agent client hints', function () { + it('should add device.sua if available', function () { + const bidderRequestWithUserAgentClientHints = { refererInfo: {}, + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + }], + mobile: 0, + model: 'Pro', + bitness: '64', + architecture: 'x86' + } + } + }}; + + let request = spec.buildRequests(bidRequests, bidderRequestWithUserAgentClientHints); + expect(request[0].data.device.sua).to.exist; + expect(request[0].data.device.sua).to.deep.equal(bidderRequestWithUserAgentClientHints.ortb2.device.sua); + const bidderRequestWithoutUserAgentClientHints = {refererInfo: {}, ortb2: {}}; + request = spec.buildRequests(bidRequests, bidderRequestWithoutUserAgentClientHints); + expect(request[0].data.device?.sua).to.not.exist; + }); + }); }); context('when there is a consent management framework', function () { @@ -624,15 +681,15 @@ describe('OpenxRtbAdapter', function () { }); it('should send a signal to specify that US Privacy applies to this request', function () { - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); }); it('should not send the regs object, when consent string is undefined', function () { delete bidderRequest.uspConsent; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.regs).to.not.have.property('ext'); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.us_privacy).to.not.exist; }); }); @@ -666,21 +723,21 @@ describe('OpenxRtbAdapter', function () { it('should send a signal to specify that GDPR applies to this request', function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(1); expect(request[1].data.regs.ext.gdpr).to.equal(1); }); it('should send the consent string', function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); it('should send the addtlConsent string', function () { bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); @@ -688,7 +745,7 @@ describe('OpenxRtbAdapter', function () { it('should send a signal to specify that GDPR does not apply to this request', function () { bidderRequest.gdprConsent.gdprApplies = false; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(0); expect(request[1].data.regs.ext.gdpr).to.equal(0); }); @@ -697,9 +754,8 @@ describe('OpenxRtbAdapter', function () { 'but can send consent data, ', function () { delete bidderRequest.gdprConsent.gdprApplies; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].data.regs).to.not.have.property('ext'); - expect(request[1].data.regs).to.not.have.property('ext'); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); @@ -707,7 +763,7 @@ describe('OpenxRtbAdapter', function () { it('when consent string is undefined, should not send the consent string, ', function () { delete bidderRequest.gdprConsent.consentString; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); expect(request[0].data.imp[0].ext.consent).to.equal(undefined); expect(request[1].data.imp[0].ext.consent).to.equal(undefined); }); @@ -716,8 +772,8 @@ describe('OpenxRtbAdapter', function () { context('coppa', function() { it('when there are no coppa param settings, should not send a coppa flag', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[0].data.regs.coppa).to.equal(0); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); + expect(request[0].data.regs?.coppa).to.be.not.ok; }); it('should send a coppa flag there is when there is coppa param settings in the bid requests', function () { @@ -729,7 +785,7 @@ describe('OpenxRtbAdapter', function () { return utils.deepAccess(mockConfig, key); }); - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.regs.coppa).to.equal(1); }); @@ -751,21 +807,21 @@ describe('OpenxRtbAdapter', function () { it('when there is a do not track, should send a dnt', function () { doNotTrackStub.returns(1); - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(1); }); it('when there is not do not track, don\'t send dnt', function () { doNotTrackStub.returns(0); - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(0); }); it('when there is no defined do not track, don\'t send dnt', function () { doNotTrackStub.returns(null); - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const request = spec.buildRequests(bidRequestsWithMediaTypes, syncAddFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(0); }); }); @@ -1081,55 +1137,57 @@ describe('OpenxRtbAdapter', function () { }); }); - context('when there is a response, the common response properties', function () { - beforeEach(function () { - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - - bidResponse = { - seatbid: [{ - bid: [{ - impid: 'test-bid-id', - price: 2, - w: 300, - h: 250, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup', - adomain: ['brand.com'], - ext: { - dsp_id: '123', - buyer_id: '456', - brand_id: '789', - paf: { - content_id: 'paf_content_id' - } - } - }] - }], - cur: 'AUS', + const SAMPLE_BID_REQUESTS = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + const SAMPLE_BID_RESPONSE = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + w: 300, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup', + adomain: ['brand.com'], ext: { + dsp_id: '123', + buyer_id: '456', + brand_id: '789', paf: { - transmission: {version: '12'} + content_id: 'paf_content_id' } } - }; + }] + }], + cur: 'AUS', + ext: { + paf: { + transmission: {version: '12'} + } + } + }; + + context('when there is a response, the common response properties', function () { + beforeEach(function () { + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; }); @@ -1196,6 +1254,23 @@ describe('OpenxRtbAdapter', function () { }); }); + context('when there is more than one response', () => { + let bids; + beforeEach(function () { + bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + bidResponse = deepClone(SAMPLE_BID_RESPONSE); + bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); + bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' + + bids = spec.interpretResponse({body: bidResponse}, bidRequest); + }); + + it('should not confuse paf content_id', () => { + expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); + }); + }) + context('when the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ @@ -1475,5 +1550,4 @@ describe('OpenxRtbAdapter', function () { }); }); }); -}) -; +}); diff --git a/test/spec/modules/optimonAnalyticsAdapter_spec.js b/test/spec/modules/optimonAnalyticsAdapter_spec.js index 406e9cb75c4..f1aa00334b5 100644 --- a/test/spec/modules/optimonAnalyticsAdapter_spec.js +++ b/test/spec/modules/optimonAnalyticsAdapter_spec.js @@ -4,6 +4,7 @@ import optimonAnalyticsAdapter from '../../../modules/optimonAnalyticsAdapter.js import adapterManager from 'src/adapterManager'; import * as events from 'src/events'; import constants from 'src/constants.json' +import {expectEvents} from '../../helpers/analytics.js'; const AD_UNIT_CODE = 'demo-adunit-1'; const PUBLISHER_CONFIG = { @@ -14,14 +15,12 @@ const PUBLISHER_CONFIG = { describe('Optimon Analytics Adapter', () => { const optmn_currentWindow = utils.getWindowSelf(); - let optmn_queue = []; beforeEach(() => { - optmn_currentWindow.OptimonAnalyticsAdapter = (...optmn_args) => optmn_queue.push(optmn_args); + optmn_currentWindow.OptimonAnalyticsAdapter = sinon.stub() adapterManager.enableAnalytics({ provider: 'optimon' }); - optmn_queue = [] }); afterEach(() => { @@ -29,13 +28,6 @@ describe('Optimon Analytics Adapter', () => { }); it('should forward all events to the queue', () => { - const optmn_arguments = [AD_UNIT_CODE, PUBLISHER_CONFIG]; - - events.emit(constants.EVENTS.AUCTION_END, optmn_arguments) - events.emit(constants.EVENTS.BID_TIMEOUT, optmn_arguments) - events.emit(constants.EVENTS.BID_WON, optmn_arguments) - - // 3 Optimon events + 1 Clean.io event - expect(optmn_queue.length).to.eql(4); + expectEvents().to.beBundledTo(optmn_currentWindow.OptimonAnalyticsAdapter); }); }); diff --git a/test/spec/modules/orbitsoftBidAdapter_spec.js b/test/spec/modules/orbitsoftBidAdapter_spec.js new file mode 100644 index 00000000000..8c3187e9324 --- /dev/null +++ b/test/spec/modules/orbitsoftBidAdapter_spec.js @@ -0,0 +1,255 @@ +import {expect} from 'chai'; +import {spec} from 'modules/orbitsoftBidAdapter.js'; + +const ENDPOINT_URL = 'https://orbitsoft.com/php/ads/hb.phps'; +const REFERRER_URL = 'http://referrer.url/?_='; + +describe('Orbitsoft adapter', function () { + describe('implementation', function () { + describe('for requests', function () { + it('should accept valid bid', function () { + let validBid = { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + }, + isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('should reject invalid bid', function () { + let invalidBid = { + bidder: 'orbitsoft' + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + describe('for requests', function () { + it('should accept valid bid with styles', function () { + let validBid = { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL, + style: { + title: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + description: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + url: { + family: 'Tahoma', + size: 'medium', + weight: 'normal', + style: 'normal', + color: '0053F9' + }, + colors: { + background: 'ffffff', + border: 'E0E0E0', + link: '5B99FE' + } + } + }, + refererInfo: {referer: REFERRER_URL}, + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + + let buildRequest = spec.buildRequests([validBid])[0]; + let requestUrl = buildRequest.url; + let requestUrlParams = buildRequest.data; + expect(requestUrl).to.equal(ENDPOINT_URL); + expect(requestUrlParams).have.property('f1', 'Tahoma'); + expect(requestUrlParams).have.property('fs1', 'medium'); + expect(requestUrlParams).have.property('w1', 'normal'); + expect(requestUrlParams).have.property('s1', 'normal'); + expect(requestUrlParams).have.property('c3', '0053F9'); + expect(requestUrlParams).have.property('f2', 'Tahoma'); + expect(requestUrlParams).have.property('fs2', 'medium'); + expect(requestUrlParams).have.property('w2', 'normal'); + expect(requestUrlParams).have.property('s2', 'normal'); + expect(requestUrlParams).have.property('c4', '0053F9'); + expect(requestUrlParams).have.property('f3', 'Tahoma'); + expect(requestUrlParams).have.property('fs3', 'medium'); + expect(requestUrlParams).have.property('w3', 'normal'); + expect(requestUrlParams).have.property('s3', 'normal'); + expect(requestUrlParams).have.property('c5', '0053F9'); + expect(requestUrlParams).have.property('c2', 'ffffff'); + expect(requestUrlParams).have.property('c1', 'E0E0E0'); + expect(requestUrlParams).have.property('c6', '5B99FE'); + }); + + it('should accept valid bid with custom params', function () { + let validBid = { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL, + customParams: { + cacheBuster: 'bf4d7c1', + clickUrl: 'http://testclickurl.com' + } + }, + refererInfo: {referer: REFERRER_URL}, + }, + isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + + let buildRequest = spec.buildRequests([validBid])[0]; + let requestUrlCustomParams = buildRequest.data; + expect(requestUrlCustomParams).have.property('c.cacheBuster', 'bf4d7c1'); + expect(requestUrlCustomParams).have.property('c.clickUrl', 'http://testclickurl.com'); + }); + + it('should reject invalid bid without requestUrl', function () { + let invalidBid = { + bidder: 'orbitsoft', + params: { + placementId: '123' + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + + it('should reject invalid bid without placementId', function () { + let invalidBid = { + bidder: 'orbitsoft', + params: { + requestUrl: ENDPOINT_URL + } + }, + isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + describe('bid responses', function () { + it('should return complete bid response', function () { + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 0.5, + width: 240, + height: 240, + content_url: 'https://orbitsoft.com/php/ads/hb.html', + adomain: ['test.adomain.tld'] + } + }; + + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + expect(bids).to.be.lengthOf(1); + expect(bids[0].cpm).to.equal(serverResponse.body.cpm); + expect(bids[0].width).to.equal(serverResponse.body.width); + expect(bids[0].height).to.equal(serverResponse.body.height); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].adUrl).to.have.length.above(1); + expect(bids[0].adUrl).to.have.string('https://orbitsoft.com/php/ads/hb.html'); + expect(Object.keys(bids[0].meta)).to.include.members(['advertiserDomains']); + expect(bids[0].meta.advertiserDomains).to.deep.equal(serverResponse.body.adomain); + }); + + it('should return empty bid response', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 0 + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on incorrect size', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = { + body: { + callback_uid: '265b29b70cc106', + cpm: 1.5, + width: 0, + height: 0 + } + }, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response with error', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = {error: 'error'}, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + + it('should return empty bid response on empty body', function () { + let bidRequests = [ + { + bidder: 'orbitsoft', + params: { + placementId: '123', + requestUrl: ENDPOINT_URL + } + } + ]; + let serverResponse = {}, + bids = spec.interpretResponse(serverResponse, {'bidRequest': bidRequests[0]}); + + expect(bids).to.be.lengthOf(0); + }); + }); + }); +}); diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index 5d7bebc1de1..f5ce00ed8df 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -1,7 +1,7 @@ -import {expect} from 'chai'; -import {spec} from 'modules/outbrainBidAdapter.js'; -import {config} from 'src/config.js'; -import {server} from 'test/mocks/xhr'; +import { expect } from 'chai'; +import { spec } from 'modules/outbrainBidAdapter.js'; +import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr'; import { createEidsArray } from 'modules/userId/eids.js'; describe('Outbrain Adapter', function () { @@ -45,6 +45,26 @@ describe('Outbrain Adapter', function () { ] } + const videoBidRequestParams = { + mediaTypes: { + video: { + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + } + describe('isBidRequestValid', function () { before(() => { config.setConfig({ @@ -93,6 +113,34 @@ describe('Outbrain Adapter', function () { } expect(spec.isBidRequestValid(bid)).to.equal(true) }) + it('should succeed when bid contains video', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + ...videoBidRequestParams, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + it('should fail when bid contains insufficient video information', function () { + const bid = { + bidder: 'outbrain', + params: { + publisher: { + id: 'publisher-id', + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + }, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) it('should fail if publisher id is not set', function () { const bid = { bidder: 'outbrain', @@ -298,6 +346,61 @@ describe('Outbrain Adapter', function () { expect(res.data).to.deep.equal(JSON.stringify(expectedData)) }) + it('should build video request', function () { + const bidRequest = { + ...commonBidRequest, + ...videoBidRequestParams, + } + const expectedData = { + site: { + page: 'https://example.com/', + publisher: { + id: 'publisher-id' + } + }, + device: { + ua: navigator.userAgent + }, + source: { + fd: 1 + }, + cur: [ + 'USD' + ], + imp: [ + { + id: '1', + video: { + w: 640, + h: 480, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [1], + mimes: ['video/mp4'], + skip: 1, + api: [2], + minbitrate: 1000, + maxbitrate: 3000, + minduration: 3, + maxduration: 10, + startdelay: 2, + placement: 4, + linearity: 1 + } + } + ], + ext: { + prebid: { + channel: { + name: 'pbjs', version: '$prebid.version$' + } + } + } + } + const res = spec.buildRequests([bidRequest], commonBidderRequest) + expect(res.url).to.equal('https://bidder-url.com') + expect(res.data).to.deep.equal(JSON.stringify(expectedData)) + }) + it('should pass optional parameters in request', function () { const bidRequest = { ...commonBidRequest, @@ -390,7 +493,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - config.setConfig({coppa: true}) + config.setConfig({ coppa: true }) const res = spec.buildRequests([bidRequest], commonBidderRequest) const resData = JSON.parse(res.data) @@ -412,7 +515,7 @@ describe('Outbrain Adapter', function () { let res = spec.buildRequests([bidRequest], commonBidderRequest); const resData = JSON.parse(res.data) expect(resData.user.ext.eids).to.deep.equal([ - {source: 'liveramp.com', uids: [{id: 'id-value', atype: 3}]} + { source: 'liveramp.com', uids: [{ id: 'id-value', atype: 3 }] } ]); }); @@ -421,7 +524,7 @@ describe('Outbrain Adapter', function () { ...commonBidRequest, ...nativeBidRequestParams, } - bidRequest.getFloor = function() { + bidRequest.getFloor = function () { return { currency: 'USD', floor: 1.23, @@ -619,6 +722,67 @@ describe('Outbrain Adapter', function () { const res = spec.interpretResponse(serverResponse, request) expect(res).to.deep.equal(expectedRes) }); + + it('should interpret video response', function () { + const serverResponse = { + body: { + id: '123', + seatbid: [ + { + bid: [ + { + id: '111', + impid: '1', + price: 1.1, + adm: '\u003cVAST version="3.0"\u003e\u003cAd\u003e\u003cInLine\u003e\u003cAdSystem\u003ezemanta\u003c/AdSystem\u003e\u003cAdTitle\u003e1\u003c/AdTitle\u003e\u003cImpression\u003ehttp://win.com\u003c/Impression\u003e\u003cImpression\u003ehttp://example.com/imptracker\u003c/Impression\u003e\u003cCreatives\u003e\u003cCreative\u003e\u003cLinear\u003e\u003cDuration\u003e00:00:25\u003c/Duration\u003e\u003cTrackingEvents\u003e\u003cTracking event="start"\u003ehttp://example.com/start\u003c/Tracking\u003e\u003cTracking event="progress" offset="00:00:03"\u003ehttp://example.com/p3s\u003c/Tracking\u003e\u003c/TrackingEvents\u003e\u003cVideoClicks\u003e\u003cClickThrough\u003ehttp://link.com\u003c/ClickThrough\u003e\u003c/VideoClicks\u003e\u003cMediaFiles\u003e\u003cMediaFile delivery="progressive" type="video/mp4" bitrate="700" width="640" height="360"\u003ehttps://example.com/123_360p.mp4\u003c/MediaFile\u003e\u003c/MediaFiles\u003e\u003c/Linear\u003e\u003c/Creative\u003e\u003c/Creatives\u003e\u003c/InLine\u003e\u003c/Ad\u003e\u003c/VAST\u003e', + adid: '100', + cid: '5', + crid: '29998660', + cat: ['cat-1'], + adomain: [ + 'example.com' + ], + nurl: 'http://example.com/win/${AUCTION_PRICE}' + } + ], + seat: '100', + group: 1 + } + ], + bidid: '456', + cur: 'USD' + } + } + const request = { + bids: [ + { + ...commonBidRequest, + ...videoBidRequestParams + } + ] + } + const expectedRes = [ + { + requestId: request.bids[0].bidId, + cpm: 1.1, + creativeId: '29998660', + ttl: 360, + netRevenue: false, + currency: 'USD', + mediaType: 'video', + nurl: 'http://example.com/win/${AUCTION_PRICE}', + vastXml: 'zemanta1http://win.comhttp://example.com/imptracker00:00:25http://example.com/starthttp://example.com/p3shttp://link.comhttps://example.com/123_360p.mp4', + meta: { + 'advertiserDomains': [ + 'example.com' + ] + }, + } + ] + + const res = spec.interpretResponse(serverResponse, request) + expect(res).to.deep.equal(expectedRes) + }); }) }) @@ -637,41 +801,41 @@ describe('Outbrain Adapter', function () { }) it('should return user sync if pixel enabled with outbrain config', function () { - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + const ret = spec.getUserSyncs({ pixelEnabled: true }) + expect(ret).to.deep.equal([{ type: 'image', url: usersyncUrl }]) }) it('should not return user sync if pixel disabled', function () { - const ret = spec.getUserSyncs({pixelEnabled: false}) + const ret = spec.getUserSyncs({ pixelEnabled: false }) expect(ret).to.be.an('array').that.is.empty }) it('should not return user sync if url is not set', function () { config.resetConfig() - const ret = spec.getUserSyncs({pixelEnabled: true}) + const ret = spec.getUserSyncs({ pixelEnabled: true }) expect(ret).to.be.an('array').that.is.empty }) - it('should pass GDPR consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + it('should pass GDPR consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` }]); }); - it('should pass US consent', function() { + it('should pass US consent', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` }]); }); - it('should pass GDPR and US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + it('should pass GDPR and US consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }]); }); diff --git a/test/spec/modules/oxxionRtdProvider_spec.js b/test/spec/modules/oxxionRtdProvider_spec.js new file mode 100644 index 00000000000..776076cc215 --- /dev/null +++ b/test/spec/modules/oxxionRtdProvider_spec.js @@ -0,0 +1,142 @@ +import {oxxionSubmodule} from 'modules/oxxionRtdProvider.js'; +import 'src/prebid.js'; + +const utils = require('src/utils.js'); + +const moduleConfig = { + params: { + domain: 'test.endpoint', + contexts: ['instream', 'outstream'] + } +}; + +let request = { + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'timestamp': 1647424261187, + 'auctionEnd': 1647424261714, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': 'msq_tag_200124_banner', + 'mediaTypes': { 'banner': { 'sizes': [[300, 600]] } }, + 'bids': [{'bidder': 'appnexus', 'params': {'placementId': 123456}}], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' + }, + { + 'code': 'msq_tag_200125_video', + 'mediaTypes': { 'video': { 'context': 'instream' }, playerSize: [640, 480], mimes: ['video/mp4'] }, + 'bids': [ + {'bidder': 'mediasquare', 'params': {'code': 'publishername_atf_desktop_rg_video', 'owner': 'test'}}, + {'bidder': 'appnexusAst', 'params': {'placementId': 345678}}, + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b41' + }, + ] +}; + +let bids = [{ + 'bidderCode': 'mediasquare', + 'width': 640, + 'height': 480, + 'statusMessage': 'Bid available', + 'adId': '3647626fdbe68a', + 'requestId': '2d891705d2125b', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b41', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'video', + 'source': 'client', + 'cpm': 0.9723, + 'creativeId': 'freewheel|AdswizzAd71819', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'mediasquare': { + 'bidder': 'freewheel', + 'code': 'test/publishername_atf_desktop_rg_video', + 'hasConsent': true + }, + 'meta': { + 'advertiserDomains': [ + 'unknown' + ] + }, + 'vastUrl': 'https://some.vast-url.com', + 'vastXml': '', + 'adapterCode': 'mediasquare', + 'originalCpm': 0.9723, + 'originalCurrency': 'USD', + 'responseTimestamp': 1665505150740, + 'requestTimestamp': 1665505150594, + 'bidder': 'mediasquare', + 'adUnitCode': 'msq_tag_200125_video', + 'timeToRespond': 146, + 'size': '640x480', +}, { + 'bidderCode': 'appnexusAst', + 'width': 640, + 'height': 480, + 'statusMessage': 'Bid available', + 'adId': '4b2e1581c0ca1a', + 'requestId': '2d891705d2125b', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b41', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'video', + 'source': 'client', + 'cpm': 1.9723, + 'creativeId': '159080650', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'vastUrl': 'https://some.vast-url.com', + 'vastXml': 'AdnxsTitle', + 'adapterCode': 'mediasquare', + 'originalCpm': 1.9723, + 'originalCurrency': 'USD', + 'responseTimestamp': 1665505150740, + 'requestTimestamp': 1665505150594, + 'bidder': 'appnexusAst', + 'adUnitCode': 'msq_tag_200125_video', + 'timeToRespond': 146, + 'size': '640x480', + 'vastImpUrl': 'https://some.tracking-url.com' +}, +]; + +describe('oxxionRtdProvider', () => { + describe('Oxxion RTD sub module', () => { + it('should init, return true, and set the params', () => { + expect(oxxionSubmodule.init(moduleConfig)).to.equal(true); + }); + }); + + describe('Oxxion RTD sub module', () => { + let auctionEnd = request; + auctionEnd.bidsReceived = bids; + it('call everything', function() { + oxxionSubmodule.getBidRequestData(request, null, moduleConfig); + oxxionSubmodule.onAuctionEndEvent(auctionEnd, moduleConfig); + }); + it('check vastImpUrl', function() { + expect(auctionEnd.bidsReceived[0]).to.have.property('vastImpUrl'); + let expectVastImpUrl = 'https://' + moduleConfig.params.domain + '.oxxion.io/analytics/vast_imp?'; + expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(expectVastImpUrl); + expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(encodeURI('https://some.tracking-url.com')); + }); + it('check vastXml', function() { + expect(auctionEnd.bidsReceived[0]).to.have.property('vastXml'); + let vastWrapper = new DOMParser().parseFromString(auctionEnd.bidsReceived[0].vastXml, 'text/xml'); + let impressions = vastWrapper.querySelectorAll('VAST Ad Wrapper Impression'); + expect(impressions.length).to.equal(2); + expect(auctionEnd.bidsReceived[1]).to.have.property('vastXml'); + expect(auctionEnd.bidsReceived[1].adId).to.equal('4b2e1581c0ca1a'); + let vastInline = new DOMParser().parseFromString(auctionEnd.bidsReceived[1].vastXml, 'text/xml'); + let inline = vastInline.querySelectorAll('VAST Ad InLine'); + expect(inline).to.have.lengthOf(1); + let inlineImpressions = vastInline.querySelectorAll('VAST Ad InLine Impression'); + expect(inlineImpressions).to.have.lengthOf.above(0); + }); + it('check cpmIncrement', function() { + expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(encodeURI('cpmIncrement=1')); + }); + }); +}); diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 9282ec3ac24..5030e662ea9 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -13,13 +13,13 @@ import { deepAccess, deepSetValue, mergeDeep } from '../../../src/utils.js' import { config } from 'src/config.js' describe('permutiveRtdProvider', function () { - before(function () { + beforeEach(function () { const data = getTargetingData() setLocalStorage(data) config.resetConfig() }) - after(function () { + afterEach(function () { const data = getTargetingData() removeLocalStorage(data) config.resetConfig() @@ -242,6 +242,42 @@ describe('permutiveRtdProvider', function () { }) }) it('should not overwrite ortb2 config', function () { + const moduleConfig = getConfig() + const acBidders = moduleConfig.params.acBidders + const sampleOrtbConfig = { + site: { + name: 'example' + }, + user: { + data: [ + { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ id: '687' }, { id: '123' }] + } + ] + } + } + + const bidderConfig = Object.fromEntries(acBidders.map(bidder => [bidder, sampleOrtbConfig])) + + const transformedUserData = { + name: 'transformation', + ext: { test: true }, + segment: [1, 2, 3] + } + + setBidderRtb(bidderConfig, moduleConfig, { + // TODO: this argument is unused, is the test still valid / needed? + testTransformation: userData => transformedUserData + }) + + acBidders.forEach(bidder => { + expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) + expect(bidderConfig[bidder].user.data).to.deep.include.members([sampleOrtbConfig.user.data[0]]) + }) + }) + it('should update user.keywords and not override existing values', function () { const moduleConfig = getConfig() const acBidders = moduleConfig.params.acBidders const sampleOrtbConfig = { @@ -275,10 +311,65 @@ describe('permutiveRtdProvider', function () { acBidders.forEach(bidder => { expect(bidderConfig[bidder].site.name).to.equal(sampleOrtbConfig.site.name) - expect(bidderConfig[bidder].user.keywords).to.equal(sampleOrtbConfig.user.keywords) expect(bidderConfig[bidder].user.data).to.deep.include.members([sampleOrtbConfig.user.data[0]]) + expect(bidderConfig[bidder].user.keywords).to.deep.equal('a,b,p_standard_aud=123,p_standard_aud=abc') }) }) + it('should merge ortb2 correctly for ac and ssps', function () { + setLocalStorage({ + '_ppam': [], + '_psegs': [], + '_pcrprs': ['abc', 'def', 'xyz'], + '_pssps': { + ssps: ['foo', 'bar'], + cohorts: ['xyz', 'uvw'], + } + }) + const moduleConfig = { + name: 'permutive', + waitForIt: true, + params: { + acBidders: ['foo', 'other'], + maxSegs: 30 + } + } + const bidderConfig = {}; + + setBidderRtb(bidderConfig, moduleConfig) + + // include both ac and ssp cohorts, as foo is both in ac bidders and ssps + const expectedFooTargetingData = [ + { id: 'abc' }, + { id: 'def' }, + { id: 'xyz' }, + { id: 'uvw' }, + ] + expect(bidderConfig['foo'].user.data).to.deep.include.members([{ + name: 'permutive.com', + segment: expectedFooTargetingData + }]) + + // don't include ac targeting as it's not in ac bidders + const expectedBarTargetingData = [ + { id: 'xyz' }, + { id: 'uvw' }, + ] + expect(bidderConfig['bar'].user.data).to.deep.include.members([{ + name: 'permutive.com', + segment: expectedBarTargetingData + }]) + + // only include ac targeting as this ssp is not in ssps list + const expectedOtherTargetingData = [ + { id: 'abc' }, + { id: 'def' }, + { id: 'xyz' }, + ] + expect(bidderConfig['other'].user.data).to.deep.include.members([{ + name: 'permutive.com', + segment: expectedOtherTargetingData + }]) + }) }) describe('Getting segments', function () { @@ -291,7 +382,11 @@ describe('permutiveRtdProvider', function () { const segments = getSegments(max) for (const key in segments) { - expect(segments[key]).to.have.length(max) + if (key === 'ssp') { + expect(segments[key].cohorts).to.have.length(max) + } else { + expect(segments[key]).to.have.length(max) + } } }) }) @@ -311,12 +406,13 @@ describe('permutiveRtdProvider', function () { if (bidder === 'appnexus') { expect(deepAccess(params, 'keywords.permutive')).to.eql(data.appnexus) - expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac) + expect(deepAccess(params, 'keywords.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) } }) }) } }) + it('sets segment targeting for Magnite', function () { const data = transformedTargeting() const adUnits = getAdUnits() @@ -331,12 +427,44 @@ describe('permutiveRtdProvider', function () { if (bidder === 'rubicon') { expect(deepAccess(params, 'visitor.permutive')).to.eql(data.rubicon) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac) + expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) + } + }) + }) + } + }) + + it('sets segment targeting for Magnite video', function () { + const targetingData = getTargetingData() + targetingData._prubicons.push(321) + + setLocalStorage(targetingData) + + const data = transformedTargeting(targetingData) + const config = getConfig() + + const adUnits = getAdUnits().filter(adUnit => adUnit.mediaTypes.video) + expect(adUnits).to.have.lengthOf(1) + + initSegments({ adUnits }, callback, config) + + function callback() { + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + const { bidder, params } = bid + + if (bidder === 'rubicon') { + expect( + deepAccess(params, 'visitor.permutive'), + 'Should map all targeting values to a string', + ).to.eql(data.rubicon.map(String)) + expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) } }) }) } }) + it('sets segment targeting for Ozone', function () { const data = transformedTargeting() const adUnits = getAdUnits() @@ -350,7 +478,7 @@ describe('permutiveRtdProvider', function () { const { bidder, params } = bid if (bidder === 'ozone') { - expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac) + expect(deepAccess(params, 'customData.0.targeting.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) } }) }) @@ -384,7 +512,7 @@ describe('permutiveRtdProvider', function () { if (bidder === 'rubicon') { expect(deepAccess(params, 'visitor.permutive')).to.eql(data.gam) - expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac) + expect(deepAccess(params, 'visitor.p_standard')).to.eql(data.ac.concat(data.ssp.cohorts)) } }) }) @@ -516,14 +644,13 @@ function getConfig () { } } -function transformedTargeting () { - const data = getTargetingData() - +function transformedTargeting (data = getTargetingData()) { return { ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)], appnexus: data._papns, rubicon: data._prubicons, gam: data._pdfps, + ssp: data._pssps, } } @@ -534,7 +661,8 @@ function getTargetingData () { _papns: ['appnexus1', 'appnexus2'], _psegs: ['1234', '1000001', '1000002'], _ppam: ['ppam1', 'ppam2'], - _pcrprs: ['pcrprs1', 'pcrprs2'] + _pcrprs: ['pcrprs1', 'pcrprs2', 'dup'], + _pssps: { ssps: ['xyz', 'abc', 'dup'], cohorts: ['123', 'abc'] } } } @@ -640,6 +768,34 @@ function getAdUnits () { } } ] + }, + { + code: 'myVideoAdUnit', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [2, 3, 5, 6], + api: [2], + maxduration: 30, + linearity: 1 + } + }, + bids: [{ + bidder: 'rubicon', + params: { + accountId: '9840', + siteId: '123564', + zoneId: '583584', + video: { + language: 'en' + }, + visitor: { + test_kv: ['true'] + } + } + }] } ] } diff --git a/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js b/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js index 49b048f1fe6..0c4949264a7 100644 --- a/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js +++ b/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js @@ -52,19 +52,12 @@ describe('Piano DMP Analytics Adapter', () => { // Then const callQueue = (window.cX || {}).callQueue; - const billableEventIndex = callQueue.findIndex(([, params]) => params.eventType === constants.EVENTS.BILLABLE_EVENT); - if (billableEventIndex > -1) { - callQueue.splice(billableEventIndex, 1); - } - expect(callQueue).to.be.an('array'); - expect(callQueue.length).to.equal(testEvents.length); - - callQueue.forEach(([method, params], index) => { + testEvents.forEach(({event, args}) => { + const [method, params] = callQueue.filter(item => item[1].eventType === event)[0]; expect(method).to.equal('prebid'); - expect(params.eventType).to.equal(testEvents[index].event); - expect(params.params).to.deep.equal(testEvents[index].args); - }); + expect(params.params).to.deep.equal(args); + }) }); }); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index c3ca3261193..0ce060b9904 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -1,24 +1,37 @@ -import { expect } from 'chai'; -import { PrebidServer as Adapter, resetSyncedStatus, resetWurlMap } from 'modules/prebidServerBidAdapter/index.js'; +/* eslint-disable no-trailing-spaces */ +import {expect} from 'chai'; +import { + PrebidServer as Adapter, + resetSyncedStatus, + resetWurlMap, + s2sDefaultConfig +} from 'modules/prebidServerBidAdapter/index.js'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import { ajax } from 'src/ajax.js'; -import { config } from 'src/config.js'; +import {deepAccess, deepClone} from 'src/utils.js'; +import {ajax} from 'src/ajax.js'; +import {config} from 'src/config.js'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; -import { server } from 'test/mocks/xhr.js'; -import { createEidsArray } from 'modules/userId/eids.js'; -import { deepAccess, deepClone } from 'src/utils.js'; -import 'modules/appnexusBidAdapter.js' // appnexus alias test -import 'modules/rubiconBidAdapter.js' // rubicon alias test -import 'src/prebid.js' // $$PREBID_GLOBAL$$.aliasBidder test -import 'modules/currency.js' // adServerCurrency test -import { hook } from '../../../src/hook.js'; -import { decorateAdUnitsWithNativeParams } from '../../../src/native.js'; -import { auctionManager } from '../../../src/auctionManager.js'; -import { stubAuctionIndex } from '../../helpers/indexStub.js'; -import { registerBidder } from 'src/adapters/bidderFactory.js'; +import {server} from 'test/mocks/xhr.js'; +import {createEidsArray} from 'modules/userId/eids.js'; +import 'modules/appnexusBidAdapter.js'; // appnexus alias test +import 'modules/rubiconBidAdapter.js'; // rubicon alias test +import 'src/prebid.js'; // $$PREBID_GLOBAL$$.aliasBidder test +import 'modules/currency.js'; // adServerCurrency test +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import {hook} from '../../../src/hook.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {stubAuctionIndex} from '../../helpers/indexStub.js'; +import {registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; +import {syncAddFPDEnrichments, syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; let CONFIG = { accountId: '1', @@ -531,11 +544,23 @@ const RESPONSE_OPENRTB_NATIVE = { ] }; +function addFpdEnrichmentsToS2SRequest(s2sReq, bidderRequests) { + return { + ...s2sReq, + ortb2Fragments: { + ...(s2sReq.ortb2Fragments || {}), + global: syncAddFPDToBidderRequest({...(bidderRequests?.[0] || {}), ortb2: s2sReq.ortb2Fragments?.global || {}}).ortb2 + } + } +} + describe('S2S Adapter', function () { let adapter, addBidResponse = sinon.spy(), done = sinon.spy(); + addBidResponse.reject = sinon.spy(); + function prepRequest(req) { req.ad_units.forEach((adUnit) => { delete adUnit.nativeParams @@ -550,6 +575,7 @@ describe('S2S Adapter', function () { beforeEach(function () { config.resetConfig(); + config.setConfig({floors: {enabled: false}}); adapter = new Adapter(); BID_REQUESTS = [ { @@ -595,6 +621,7 @@ describe('S2S Adapter', function () { afterEach(function () { addBidResponse.resetHistory(); + addBidResponse.reject = sinon.spy(); done.resetHistory(); }); @@ -617,6 +644,14 @@ describe('S2S Adapter', function () { expect(requestBid.source.tid).to.equal(BID_REQUESTS[0].auctionId); }); + it('should set tmax to s2sConfig.timeout', () => { + const cfg = {...CONFIG, timeout: 123}; + config.setConfig({s2sConfig: cfg}); + adapter.callBids({...REQUEST, s2sConfig: cfg}, BID_REQUESTS, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.tmax).to.eql(123); + }) + it('should block request if config did not define p1Consent URL in endpoint object config', function () { let badConfig = utils.deepClone(CONFIG); badConfig.endpoint = { noP1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }; @@ -741,7 +776,7 @@ describe('S2S Adapter', function () { let gdprBidRequest = utils.deepClone(BID_REQUESTS); gdprBidRequest[0].gdprConsent = mockConsent(); - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, gdprBidRequest), gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.gdpr).is.equal(1); @@ -750,7 +785,7 @@ describe('S2S Adapter', function () { config.resetConfig(); config.setConfig({ s2sConfig: CONFIG }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[1].requestBody); expect(requestBid.regs).to.not.exist; @@ -766,7 +801,7 @@ describe('S2S Adapter', function () { addtlConsent: 'superduperconsent', }); - adapter.callBids(REQUEST, gdprBidRequest, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, gdprBidRequest), gdprBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.gdpr).is.equal(1); @@ -852,13 +887,13 @@ describe('S2S Adapter', function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); }); - it('is added to ortb2 request when in bidRequest', function () { + it('is added to ortb2 request when in FPD', function () { config.setConfig({ s2sConfig: CONFIG }); let uspBidRequest = utils.deepClone(BID_REQUESTS); uspBidRequest[0].uspConsent = '1NYN'; - adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, uspBidRequest), uspBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); @@ -866,7 +901,7 @@ describe('S2S Adapter', function () { config.resetConfig(); config.setConfig({ s2sConfig: CONFIG }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[1].requestBody); expect(requestBid.regs).to.not.exist; @@ -904,7 +939,7 @@ describe('S2S Adapter', function () { consentBidRequest[0].uspConsent = '1NYN'; consentBidRequest[0].gdprConsent = mockConsent(); - adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, consentBidRequest), consentBidRequest, addBidResponse, done, ajax); let requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); @@ -952,13 +987,13 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.device).to.deep.equal({ + sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', w: window.innerWidth, h: window.innerHeight - }); + }) expect(requestBid.app).to.deep.equal({ bundle: 'com.test.app', publisher: { 'id': '1' } @@ -979,13 +1014,13 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.device).to.deep.equal({ + sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', w: window.innerWidth, h: window.innerHeight - }); + }) expect(requestBid.app).to.deep.equal({ bundle: 'com.test.app', publisher: { 'id': '1' } @@ -1026,6 +1061,8 @@ describe('S2S Adapter', function () { expect( BID_REQUESTS[0].bids[0].getFloor.calledWith({ currency: 'USD', + mediaType: '*', + size: '*' }) ).to.be.true; @@ -1055,6 +1092,8 @@ describe('S2S Adapter', function () { expect( BID_REQUESTS[0].bids[0].getFloor.calledWith({ currency: 'USD', + mediaType: '*', + size: '*' }) ).to.be.true; @@ -1082,6 +1121,8 @@ describe('S2S Adapter', function () { expect( BID_REQUESTS[0].bids[0].getFloor.calledWith({ currency: 'JPY', + mediaType: '*', + size: '*' }) ).to.be.true; }); @@ -1247,9 +1288,9 @@ describe('S2S Adapter', function () { conversionFn: null }, 'is not working': { - expectDesc: 'first', - expectedFloor: 2, - expectedCur: '1', + expectDesc: 'absolute minimum', + expectedFloor: 1, + expectedCur: '10', conversionFn: () => { throw new Error(); } @@ -1288,6 +1329,47 @@ describe('S2S Adapter', function () { if (FEATURES.NATIVE) { describe('native requests', function () { + const ORTB_NATIVE_REQ = { + 'ver': '1.2', + 'assets': [ + { + 'required': 1, + 'id': 0, + 'title': { + 'len': 800 + } + }, + { + 'required': 1, + 'id': 1, + 'img': { + 'type': 3, + 'w': 989, + 'h': 742 + } + }, + { + 'required': 1, + 'id': 2, + 'img': { + 'type': 1, + 'wmin': 10, + 'hmin': 10, + 'ext': { + 'aspectratios': ['1:1'] + } + } + }, + { + 'required': 1, + 'id': 3, + 'data': { + 'type': 1 + } + } + ] + }; + it('adds device.w and device.h even if the config lacks a device object', function () { const _config = { s2sConfig: CONFIG, @@ -1295,16 +1377,17 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); - expect(requestBid.device).to.deep.equal({ + sinon.assert.match(requestBid.device, { w: window.innerWidth, h: window.innerHeight - }); + }) expect(requestBid.app).to.deep.equal({ bundle: 'com.test.app', publisher: { 'id': '1' } }); + expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); it('adds native request for OpenRTB', function () { @@ -1313,54 +1396,17 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids({...REQUEST, s2sConfig: Object.assign({}, CONFIG, s2sDefaultConfig)}, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); const ortbReq = JSON.parse(requestBid.imp[0].native.request); expect(ortbReq).to.deep.equal({ - 'ver': '1.2', + ...ORTB_NATIVE_REQ, 'context': 1, 'plcmttype': 1, 'eventtrackers': [{ event: 1, methods: [1] }], - 'assets': [ - { - 'required': 1, - 'id': 0, - 'title': { - 'len': 800 - } - }, - { - 'required': 1, - 'id': 1, - 'img': { - 'type': 3, - 'w': 989, - 'h': 742 - } - }, - { - 'required': 1, - 'id': 2, - 'img': { - 'type': 1, - 'wmin': 10, - 'hmin': 10, - 'ext': { - 'aspectratios': ['1:1'] - } - } - }, - { - 'required': 1, - 'id': 3, - 'data': { - 'type': 1 - } - } - ] }); expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); @@ -1385,6 +1431,29 @@ describe('S2S Adapter', function () { expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); + it('can override default values for imp.native.request with s2sConfig.ortbNative', () => { + const cfg = { + ...CONFIG, + ortbNative: { + eventtrackers: [ + {event: 1, methods: [1, 2]} + ] + } + } + config.setConfig({ + s2sConfig: cfg + }); + adapter.callBids({...REQUEST, s2sConfig: cfg}, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[0].requestBody); + const ortbReq = JSON.parse(requestBid.imp[0].native.request); + expect(ortbReq).to.eql({ + ...ORTB_NATIVE_REQ, + eventtrackers: [ + {event: 1, methods: [1, 2]} + ] + }) + }) + it('should not include ext.aspectratios if adunit\'s aspect_ratios do not define radio_width and ratio_height', () => { const req = deepClone(REQUEST); req.ad_units[0].mediaTypes.native.icon.aspect_ratios[0] = {'min_width': 1, 'min_height': 2}; @@ -1415,7 +1484,7 @@ describe('S2S Adapter', function () { }; config.setConfig(_config); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); expect(requestBid.site.publisher).to.exist.and.to.be.a('object'); @@ -1432,22 +1501,46 @@ describe('S2S Adapter', function () { content: { language: 'en' }, + domain: 'mytestpage.com', page: 'http://mytestpage.com' }); }); + it('site should not be present when app is present', function () { + const _config = { + s2sConfig: CONFIG, + app: { bundle: 'com.test.app' }, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + }; + + config.setConfig(_config); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.site).to.not.exist; + expect(requestBid.app).to.exist.and.to.be.a('object'); + }); + it('adds appnexus aliases to request', function () { config.setConfig({ s2sConfig: CONFIG }); const aliasBidder = { bidder: 'beintoo', + bid_id: REQUEST.ad_units[0].bids[0].bid_id, params: { placementId: '123456' } }; const request = utils.deepClone(REQUEST); request.ad_units[0].bids = [aliasBidder]; - adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(request, [{...BID_REQUESTS[0], bidderCode: 'beintoo'}], addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext).to.haveOwnProperty('prebid'); @@ -1463,12 +1556,38 @@ describe('S2S Adapter', function () { }); }); + it('unregistered bidder should alias', function () { + const adjustedConfig = utils.deepClone(CONFIG); + adjustedConfig.bidders = 'bidderD' + config.setConfig({ s2sConfig: adjustedConfig }); + + const aliasBidder = { + ...REQUEST.ad_units[0].bids[0], + bidder: 'bidderD', + params: { + unit: '10433394', + } + }; + + $$PREBID_GLOBAL$$.aliasBidder('mockBidder', aliasBidder.bidder); + + const request = utils.deepClone(REQUEST); + request.ad_units[0].bids = [aliasBidder]; + request.s2sConfig = adjustedConfig; + + adapter.callBids(request, [{...BID_REQUESTS[0], bidderCode: aliasBidder.bidder}], addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.ext.prebid.aliases).to.deep.equal({ bidderD: 'mockBidder' }); + }); + it('adds dynamic aliases to request', function () { config.setConfig({ s2sConfig: CONFIG }); const alias = 'foobar'; const aliasBidder = { bidder: alias, + bid_id: REQUEST.ad_units[0].bids[0].bid_id, params: { placementId: '123456' } }; @@ -1477,7 +1596,7 @@ describe('S2S Adapter', function () { // TODO: stub this $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias); - adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(request, [{...BID_REQUESTS[0], bidderCode: 'foobar'}], addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.ext).to.haveOwnProperty('prebid'); @@ -1509,13 +1628,14 @@ describe('S2S Adapter', function () { }) const aliasBidder = { bidder: 'bidderCodeForTestSkipBPSAlias_Alias', + bid_id: REQUEST.ad_units[0].bids[0].bid_id, params: { aid: 123 } }; const request = utils.deepClone(REQUEST); request.ad_units[0].bids = [aliasBidder]; - adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(request, [{...BID_REQUESTS[0], bidderCode: aliasBidder.bidder}], addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); @@ -1545,6 +1665,7 @@ describe('S2S Adapter', function () { const alias = 'foobar_1'; const aliasBidder = { bidder: alias, + bid_id: REQUEST.ad_units[0].bids[0].bid_id, params: { aid: 1234567 } }; @@ -1553,7 +1674,7 @@ describe('S2S Adapter', function () { // TODO: stub this $$PREBID_GLOBAL$$.aliasBidder('appnexus', alias, { skipPbsAliasing: true }); - adapter.callBids(request, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(request, [{...BID_REQUESTS[0], bidderCode: aliasBidder.bidder}], addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); @@ -1580,14 +1701,15 @@ describe('S2S Adapter', function () { }); config.setConfig({ s2sConfig: s2sConfig }); - const myRequest = utils.deepClone(REQUEST); - myRequest.ad_units[0].bids[0].params.usePaymentRule = true; - myRequest.ad_units[0].bids[0].params.keywords = { - foo: ['bar', 'baz'], - fizz: ['buzz'] - }; + Object.assign(BID_REQUESTS[0].bids[0].params, { + usePaymentRule: true, + keywords: { + foo: ['bar', 'baz'], + fizz: ['buzz'] + } + }) - adapter.callBids(myRequest, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); const requestParams = requestBid.imp[0].ext.prebid.bidder; @@ -1604,6 +1726,121 @@ describe('S2S Adapter', function () { }]); }); + describe('filterSettings', function () { + const getRequestBid = userSync => { + let cookieSyncConfig = utils.deepClone(CONFIG); + const s2sBidRequest = utils.deepClone(REQUEST); + cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; + s2sBidRequest.s2sConfig = cookieSyncConfig; + + config.setConfig({ userSync, s2sConfig: cookieSyncConfig }); + + let bidRequest = utils.deepClone(BID_REQUESTS); + adapter.callBids(s2sBidRequest, bidRequest, addBidResponse, done, ajax); + return JSON.parse(server.requests[0].requestBody); + } + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the all key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['appnexus', 'rubicon', 'pubmatic'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + }, + 'iframe': { + 'bidders': ['appnexus', 'rubicon', 'pubmatic'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and only the iframe key is present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + iframe: { + bidders: ['rubicon', 'pubmatic'], + filter: 'include' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': '*', + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['rubicon', 'pubmatic'], + 'filter': 'include' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the image and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + image: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + + it('correctly adds filterSettings to the cookie_sync request if userSync.filterSettings is present in the config and the all and iframe keys are both present in userSync.filterSettings', function () { + const userSync = { + filterSettings: { + all: { + bidders: ['triplelift', 'appnexus'], + filter: 'include' + }, + iframe: { + bidders: ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + filter: 'exclude' + } + } + }; + const requestBid = getRequestBid(userSync); + + expect(requestBid.filterSettings).to.deep.equal({ + 'image': { + 'bidders': ['triplelift', 'appnexus'], + 'filter': 'include' + }, + 'iframe': { + 'bidders': ['pulsepoint', 'triplelift', 'appnexus', 'rubicon'], + 'filter': 'exclude' + } + }); + }); + }); + it('adds limit to the cookie_sync request if userSyncLimit is greater than 0', function () { let cookieSyncConfig = utils.deepClone(CONFIG); cookieSyncConfig.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; @@ -1726,7 +1963,7 @@ describe('S2S Adapter', function () { device: device }); - adapter.callBids(s2sBidRequest, BID_REQUESTS, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1785,18 +2022,6 @@ describe('S2S Adapter', function () { expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveramp.com')[0].uids[0].id).is.equal('0000-1111-2222-3333'); }); - it('when config \'currency.adServerCurrency\' value is an array: ORTB has property \'cur\' value set to a single item array', function () { - config.setConfig({ - currency: { adServerCurrency: ['USD', 'GB', 'UK', 'AU'] }, - }); - - const bidRequests = utils.deepClone(BID_REQUESTS); - adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); - - const parsedRequestBody = JSON.parse(server.requests[0].requestBody); - expect(parsedRequestBody.cur).to.deep.equal(['USD']); - }); - it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { config.setConfig({ currency: { adServerCurrency: 'NZ' }, @@ -2130,29 +2355,22 @@ describe('S2S Adapter', function () { ]); }); - it('passes schain object in request', function () { - const bidRequests = utils.deepClone(BID_REQUESTS); - const schainObject = { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'indirectseller.com', - 'sid': '00001', - 'hp': 1 - }, + it('should "promote" the most reused bidder schain to source.ext.schain', () => { + const bidderReqs = [ + {...deepClone(BID_REQUESTS[0]), bidderCode: 'A'}, + {...deepClone(BID_REQUESTS[0]), bidderCode: 'B'}, + {...deepClone(BID_REQUESTS[0]), bidderCode: 'C'} + ]; + const chain1 = {chain: 1}; + const chain2 = {chain: 2}; - { - 'asi': 'indirectseller-2.com', - 'sid': '00002', - 'hp': 2 - } - ] - }; - bidRequests[0].bids[0].schain = schainObject; - adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); - const parsedRequestBody = JSON.parse(server.requests[0].requestBody); - expect(parsedRequestBody.source.ext.schain).to.deep.equal(schainObject); + bidderReqs[0].bids[0].schain = chain1; + bidderReqs[1].bids[0].schain = chain2; + bidderReqs[2].bids[0].schain = chain2; + + adapter.callBids(REQUEST, bidderReqs, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.source.ext.schain).to.eql(chain2); }); it('passes multibid array in request', function () { @@ -2273,7 +2491,11 @@ describe('S2S Adapter', function () { })); const commonContextExpected = utils.mergeDeep({ 'page': 'http://mytestpage.com', - 'publisher': { 'id': '1' } + 'domain': 'mytestpage.com', + 'publisher': { + 'id': '1', + 'domain': 'mytestpage.com' + } }, commonSite); const ortb2Fragments = { @@ -2281,7 +2503,7 @@ describe('S2S Adapter', function () { bidder: Object.fromEntries(allowedBidders.map(bidder => [bidder, {site, user, bcat, badv}])) }; - adapter.callBids({...s2sBidRequest, ortb2Fragments}, bidRequests, addBidResponse, done, ajax); + adapter.callBids(addFpdEnrichmentsToS2SRequest({...s2sBidRequest, ortb2Fragments}, bidRequests), bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); expect(parsedRequestBody.ext.prebid.bidderconfig).to.deep.equal(expected); expect(parsedRequestBody.site).to.deep.equal(commonContextExpected); @@ -2438,6 +2660,62 @@ describe('S2S Adapter', function () { }); }); + describe('Bidder-level ortb2Imp', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: { + ...CONFIG, + bidders: ['A', 'B'] + } + }) + }) + it('should be set on imp.ext.prebid.imp', () => { + const s2sReq = utils.deepClone(REQUEST); + s2sReq.ad_units[0].ortb2Imp = {l0: 'adUnit'}; + s2sReq.ad_units[0].bids = [ + { + bidder: 'A', + bid_id: 1, + ortb2Imp: { + l2: 'A' + } + }, + { + bidder: 'B', + bid_id: 2, + ortb2Imp: { + l2: 'B' + } + } + ]; + const bidderReqs = [ + { + ...BID_REQUESTS[0], + bidderCode: 'A', + bids: [{ + bidId: 1, + bidder: 'A' + }] + }, + { + ...BID_REQUESTS[0], + bidderCode: 'B', + bids: [{ + bidId: 2, + bidder: 'B' + }] + } + ] + adapter.callBids(s2sReq, bidderReqs, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.imp[0].l0).to.eql('adUnit'); + expect(req.imp[0].ext.prebid.imp).to.eql({ + A: {l2: 'A'}, + B: {l2: 'B'} + }); + }); + }); + describe('ext.prebid config', function () { it('should send \"imp.ext.prebid.storedrequest.id\" if \"ortb2Imp.ext.prebid.storedrequest.id\" is set', function () { const consentConfig = { s2sConfig: CONFIG }; @@ -2815,43 +3093,6 @@ describe('S2S Adapter', function () { expect(response).to.have.property('pbsBidId', '654321'); }); - it('handles response cache from ext.prebid.targeting with wurl and removes invalid targeting', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebidserverurl/openrtb2/auction?querystring=param' - } - }); - config.setConfig({ s2sConfig }); - const cacheResponse = utils.deepClone(RESPONSE_OPENRTB_VIDEO); - cacheResponse.seatbid.forEach(item => { - item.bid[0].ext.prebid.events = { - win: 'https://wurl.com?a=1&b=2' - }; - item.bid[0].ext.prebid.targeting = { - hb_uuid: 'a5ad3993', - hb_cache_host: 'prebid-cache.net', - hb_cache_path: '/cache', - hb_winurl: 'https://hbwinurl.com?a=1&b=2', - hb_bidid: '1234567890', - } - }); - - const s2sVidRequest = utils.deepClone(VIDEO_REQUEST); - s2sVidRequest.s2sConfig = s2sConfig; - - adapter.callBids(s2sVidRequest, BID_REQUESTS, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(cacheResponse)); - - sinon.assert.calledOnce(addBidResponse); - const response = addBidResponse.firstCall.args[1]; - - expect(response.adserverTargeting).to.deep.equal({ - hb_uuid: 'a5ad3993', - hb_cache_host: 'prebid-cache.net', - hb_cache_path: '/cache' - }); - }); - it('add request property pbsBidId with ext.prebid.bidid value', function () { const s2sConfig = Object.assign({}, CONFIG, { endpoint: { @@ -2903,6 +3144,16 @@ describe('S2S Adapter', function () { }); } + it('should reject invalid bids', () => { + config.setConfig({ s2sConfig: CONFIG }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const response = deepClone(RESPONSE_OPENRTB); + Object.assign(response.seatbid[0].bid[0], {w: null, h: null}); + server.requests[0].respond(200, {}, JSON.stringify(response)); + expect(addBidResponse.reject.calledOnce).to.be.true; + expect(addBidResponse.called).to.be.false; + }); + it('does not (by default) allow bids that were not requested', function () { config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -2911,6 +3162,7 @@ describe('S2S Adapter', function () { server.requests[0].respond(200, {}, JSON.stringify(response)); expect(addBidResponse.called).to.be.false; + expect(addBidResponse.reject.calledOnce).to.be.true; }); it('allows unrequested bids if config.allowUnknownBidderCodes', function () { @@ -2924,17 +3176,35 @@ describe('S2S Adapter', function () { expect(addBidResponse.calledWith(sinon.match.any, sinon.match({ bidderCode: 'unknown' }))).to.be.true; }); - it('uses "null" request\'s ID for all responses, when a null request is present', function () { - const cfg = {...CONFIG, allowUnknownBidderCodes: true}; - config.setConfig({s2sConfig: cfg}); - const req = {...REQUEST, s2sConfig: cfg, ad_units: [{...REQUEST.ad_units[0], bids: [{bidder: null, bid_id: 'testId'}]}]}; - const bidReq = {...BID_REQUESTS[0], bidderCode: null, bids: [{...BID_REQUESTS[0].bids[0], bidder: null, bidId: 'testId'}]} - adapter.callBids(req, [bidReq], addBidResponse, done, ajax); - const response = deepClone(RESPONSE_OPENRTB); - response.seatbid[0].seat = 'storedImpression'; - server.requests[0].respond(200, {}, JSON.stringify(response)); - sinon.assert.calledWith(addBidResponse, sinon.match.any, sinon.match({bidderCode: 'storedImpression', requestId: 'testId'})) - }); + describe('stored impressions', () => { + let bidReq, response; + + function mks2sReq(s2sConfig = CONFIG) { + return {...REQUEST, s2sConfig, ad_units: [{...REQUEST.ad_units[0], bids: [{bidder: null, bid_id: 'testId'}]}]}; + } + + beforeEach(() => { + bidReq = {...BID_REQUESTS[0], bidderCode: null, bids: [{...BID_REQUESTS[0].bids[0], bidder: null, bidId: 'testId'}]} + response = deepClone(RESPONSE_OPENRTB); + response.seatbid[0].seat = 'storedImpression'; + }) + + it('uses "null" request\'s ID for all responses, when a null request is present', function () { + const cfg = {...CONFIG, allowUnknownBidderCodes: true}; + config.setConfig({s2sConfig: cfg}); + adapter.callBids(mks2sReq(cfg), [bidReq], addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(response)); + sinon.assert.calledWith(addBidResponse, sinon.match.any, sinon.match({bidderCode: 'storedImpression', requestId: 'testId'})) + }); + + it('does not allow null requests (= stored impressions) if allowUnknownBidderCodes is not set', () => { + config.setConfig({s2sConfig: CONFIG}); + adapter.callBids(mks2sReq(), [bidReq], addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(response)); + expect(addBidResponse.called).to.be.false; + expect(addBidResponse.reject.calledOnce).to.be.true; + }); + }) it('copies ortb2Imp to response when there is only a null bid', () => { const cfg = {...CONFIG}; @@ -3457,11 +3727,7 @@ describe('S2S Adapter', function () { expect(requestBid.ext.prebid.floors).to.be.undefined; - // should pass of floorData is object - bidRequest[0].bids[0].floorData = { - skipped: false, - location: 'fetch', - } + config.setConfig({floors: {}}); adapter.callBids(REQUEST, bidRequest, addBidResponse, done, ajax); requestBid = JSON.parse(server.requests[1].requestBody); diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index 2e1bf4df062..522a78627d7 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -1,9 +1,8 @@ -import prebidmanagerAnalytics, { - storage -} from 'modules/prebidmanagerAnalyticsAdapter.js'; +import prebidmanagerAnalytics, {storage} from 'modules/prebidmanagerAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; +import {expectEvents} from '../../helpers/analytics.js'; let events = require('src/events'); let constants = require('src/constants.json'); @@ -95,15 +94,7 @@ describe('Prebid Manager Analytics Adapter', function () { } }); - events.emit(constants.EVENTS.AUCTION_INIT, {}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); - - // 6 Prebid Manager events + 1 Clean.io event - sinon.assert.callCount(prebidmanagerAnalytics.track, 7); + expectEvents().to.beTrackedBy(prebidmanagerAnalytics.track); }); }); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index cbdd4d179e3..f232631d73d 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1495,6 +1495,161 @@ describe('the price floors module', function () { }); }); + it('should use inverseFloorAdjustment function before bidder cpm adjustment', function () { + let functionUsed; + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Rubicon Adjustment'; + bidCpm *= 0.5; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Rubicon Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.5; + }, + }, + appnexus: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + functionUsed = 'Appnexus Adjustment'; + bidCpm *= 0.75; + if (bidResponse.mediaType === 'video') bidCpm -= 0.18; + return bidCpm; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + functionUsed = 'Appnexus Inverse'; + // if video is the only mediaType on Bid Request => add 0.18 + if (bidRequest.mediaTypes.video && Object.keys(bidRequest.mediaTypes).length === 1) bidCpm += 0.18; + return bidCpm / 0.75; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // start with banner as only mediaType + bidRequest.mediaTypes = { banner: { sizes: [[300, 250]] } }; + let appnexusBid = { + ...bidRequest, + bidder: 'appnexus', + }; + + // should be same as the adjusted calculated inverses above test (banner) + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // should use rubicon inverse + expect(functionUsed).to.equal('Rubicon Inverse'); + + // appnexus just using banner should be same + expect(appnexusBid.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.3334 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + + // now since asking for 'video' only mediaType inverse function should include the .18 + bidRequest.mediaTypes = { video: { context: 'instream' } }; + expect(bidRequest.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 2.36 + }); + + expect(functionUsed).to.equal('Rubicon Inverse'); + + // now since asking for 'video' inverse function should include the .18 + appnexusBid.mediaTypes = { video: { context: 'instream' } }; + expect(appnexusBid.getFloor({ mediaType: 'video' })).to.deep.equal({ + currency: 'USD', + floor: 1.5734 + }); + + expect(functionUsed).to.equal('Appnexus Inverse'); + }); + + it('should pass inverseFloorAdjustment the bidRequest object so it can be used', function () { + // Adjustment factors based on Bid Media Type + const mediaTypeFactors = { + banner: 0.5, + native: 0.7, + video: 0.9 + } + getGlobal().bidderSettings = { + rubicon: { + bidCpmAdjustment: function (bidCpm, bidResponse) { + return bidCpm * mediaTypeFactors[bidResponse.mediaType]; + }, + inverseBidAdjustment: function (bidCpm, bidRequest) { + // For the inverse we add up each mediaType in the request and divide by number of Mt's to get the inverse number + let factor = Object.keys(bidRequest.mediaTypes).reduce((sum, mediaType) => sum += mediaTypeFactors[mediaType], 0); + factor = factor / Object.keys(bidRequest.mediaTypes).length; + return bidCpm / factor; + }, + } + }; + + _floorDataForAuction[bidRequest.auctionId] = utils.deepClone(basicFloorConfig); + + _floorDataForAuction[bidRequest.auctionId].data.values = { '*': 1.0 }; + + // banner only should be 2 + bidRequest.mediaTypes = { banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 2.0 + }); + + // native only should be 1.4286 + bidRequest.mediaTypes = { native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video only should be 1.1112 + bidRequest.mediaTypes = { video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.1112 + }); + + // video and banner should even out to 0.7 factor so 1.4286 + bidRequest.mediaTypes = { video: {}, banner: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + + // video and native should even out to 0.8 factor so -- 1.25 + bidRequest.mediaTypes = { video: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.25 + }); + + // banner and native should even out to 0.6 factor so -- 1.6667 + bidRequest.mediaTypes = { banner: {}, native: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.6667 + }); + + // all 3 banner video and native should even out to 0.7 factor so -- 1.4286 + bidRequest.mediaTypes = { banner: {}, native: {}, video: {} }; + expect(bidRequest.getFloor()).to.deep.equal({ + currency: 'USD', + floor: 1.4286 + }); + }); + it('should use standard cpmAdjustment if no bidder cpmAdjustment', function () { getGlobal().bidderSettings = { rubicon: { @@ -1665,7 +1820,7 @@ describe('the price floors module', function () { }); describe('bidResponseHook tests', function () { const AUCTION_ID = '123456'; - let returnedBidResponse, indexStub; + let returnedBidResponse, indexStub, reject; let adUnit = { transactionId: 'au', code: 'test_div_1' @@ -1681,6 +1836,7 @@ describe('the price floors module', function () { }; beforeEach(function () { returnedBidResponse = {}; + reject = sinon.stub().returns({status: 'rejected'}); indexStub = sinon.stub(auctionManager, 'index'); indexStub.get(() => stubAuctionIndex({adUnits: [adUnit]})); }); @@ -1693,7 +1849,7 @@ describe('the price floors module', function () { let next = (adUnitCode, bid) => { returnedBidResponse = bid; }; - addBidResponseHook(next, bidResp.adUnitCode, Object.assign(createBid(CONSTANTS.STATUS.GOOD, {auctionId: AUCTION_ID}), bidResp)); + addBidResponseHook(next, bidResp.adUnitCode, Object.assign(createBid(CONSTANTS.STATUS.GOOD, {auctionId: AUCTION_ID}), bidResp), reject); }; it('continues with the auction if not floors data is present without any flooring', function () { runBidResponse(); @@ -1710,9 +1866,8 @@ describe('the price floors module', function () { _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 1.0 }; runBidResponse(); - expect(returnedBidResponse).to.haveOwnProperty('floorData'); - expect(returnedBidResponse.status).to.equal(CONSTANTS.BID_STATUS.BID_REJECTED); - expect(returnedBidResponse.cpm).to.equal(0); + expect(reject.calledOnce).to.be.true; + expect(returnedBidResponse.status).to.equal('rejected'); }); it('if it finds a rule and does not floor should update the bid accordingly', function () { _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index 5b9f35c5bcf..bcddb9e8b04 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -55,9 +55,9 @@ describe('ProxistoreBidAdapter', function () { }); describe('buildRequests', function () { const url = { - cookieBase: 'https://abs.proxistore.com/v3/rtb/prebid/multi', + cookieBase: 'https://api.proxistore.com/v3/rtb/prebid/multi', cookieLess: - 'https://abs.cookieless-proxistore.com/v3/rtb/prebid/multi', + 'https://api.cookieless-proxistore.com/v3/rtb/prebid/multi', }; let request = spec.buildRequests([bid], bidderRequest); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index fa4519c84e1..4ad048fef9a 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -68,6 +68,14 @@ const BID = { 'hb_size': '640x480', 'hb_source': 'server' }, + 'floorData': { + 'cpmAfterAdjustments': 6.3, + 'enforcements': {'enforceJS': true, 'enforcePBS': false, 'floorDeals': false, 'bidAdjustment': true}, + 'floorCurrency': 'USD', + 'floorRule': 'banner', + 'floorRuleValue': 1.1, + 'floorValue': 1.1 + }, getStatusCode() { return 1; } @@ -200,7 +208,18 @@ const MOCK = { 'bidId': '3bd4ebb1c900e2', 'seatBidId': 'aaaa-bbbb-cccc-dddd', 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'floorData': { + 'fetchStatus': 'success', + 'floorMin': undefined, + 'floorProvider': 'pubmatic', + 'location': 'fetch', + 'modelTimestamp': undefined, + 'modelVersion': 'floorModelTest', + 'modelWeight': undefined, + 'skipRate': 0, + 'skipped': false + } } ], 'auctionStart': 1519149536560, @@ -343,10 +362,13 @@ describe('pubmatic analytics adapter', function () { expect(data.orig).to.equal('www.test.com'); expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); + expect(data.fmv).to.equal('floorModelTest'); + expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); @@ -370,8 +392,10 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].af).to.equal('video'); expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); + expect(data.s[0].ps[0].frv).to.equal(undefined); // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -397,6 +421,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.s[1].ps[0].frv).to.equal(undefined); // tracker slot1 let firstTracker = requests[0].url; @@ -446,11 +471,14 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.pubid).to.equal('9999'); expect(data.pid).to.equal('1111'); + expect(data.fmv).to.equal('floorModelTest'); + expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); expect(data.tgid).to.equal(0); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); @@ -464,6 +492,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].af).to.equal('video'); expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); + expect(data.s[1].ps[0].frv).to.equal(undefined); // tracker slot1 let firstTracker = requests[0].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); @@ -515,6 +544,8 @@ describe('pubmatic analytics adapter', function () { expect(data.pubid).to.equal('9999'); expect(data.pid).to.equal('1111'); expect(data.tgid).to.equal(0);// test group id should be between 0-15 else set to 0 + expect(data.fmv).to.equal('floorModelTest'); + expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -563,6 +594,7 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.tgid).to.equal(0);// test group id should be an INT between 0-15 else set to 0 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -586,6 +618,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal(undefined); expect(data.s[1].ps[0].ocpm).to.equal(0); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.s[1].ps[0].frv).to.equal(undefined); }); it('Logger: post-timeout check without bid response', function() { @@ -643,6 +676,7 @@ describe('pubmatic analytics adapter', function () { let request = requests[0]; let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -667,6 +701,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.s[1].ps[0].frv).to.equal(undefined); }); it('Logger: currency conversion check', function() { @@ -748,6 +783,7 @@ describe('pubmatic analytics adapter', function () { expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -772,6 +808,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.s[1].ps[0].frv).to.equal(undefined); expect(data.dvc).to.deep.equal({'plt': 2}); // respective tracker slot let firstTracker = requests[1].url; @@ -829,6 +866,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); expect(data.dvc).to.deep.equal({'plt': 1}); + expect(data.s[1].ps[0].frv).to.equal(undefined); // respective tracker slot let firstTracker = requests[1].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); @@ -856,6 +894,7 @@ describe('pubmatic analytics adapter', function () { expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -880,6 +919,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.s[1].ps[0].frv).to.equal(undefined); // respective tracker slot let firstTracker = requests[1].url; expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); @@ -979,11 +1019,14 @@ describe('pubmatic analytics adapter', function () { expect(data.orig).to.equal('www.test.com'); expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); + expect(data.fmv).to.equal('floorModelTest'); + expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); @@ -1007,9 +1050,11 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].af).to.equal('video'); expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); + expect(data.s[0].ps[0].frv).to.equal(1.1); // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); + expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1035,6 +1080,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].af).to.equal('banner'); expect(data.s[1].ps[0].ocpm).to.equal(1.52); expect(data.s[1].ps[0].ocry).to.equal('USD'); + expect(data.s[1].ps[0].frv).to.equal(undefined); // tracker slot1 let firstTracker = requests[0].url; @@ -1091,11 +1137,14 @@ describe('pubmatic analytics adapter', function () { expect(data.orig).to.equal('www.test.com'); expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); + expect(data.fmv).to.equal('floorModelTest'); + expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); @@ -1119,6 +1168,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].af).to.equal('video'); expect(data.s[0].ps[0].ocpm).to.equal(1.23); expect(data.s[0].ps[0].ocry).to.equal('USD'); + expect(data.s[0].ps[0].frv).to.equal(1.1); // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 3f341fd259f..fa8f809e8a2 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,7 +1,7 @@ -import {expect} from 'chai'; -import {spec, checkVideoPlacement, _getDomainFromURL} from 'modules/pubmaticBidAdapter.js'; +import { expect } from 'chai'; +import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier } from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; -import {config} from 'src/config.js'; +import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { bidderSettings } from 'src/bidderSettings.js'; const constants = require('src/constants.json'); @@ -2607,6 +2607,21 @@ describe('PubMatic adapter', function () { expect(data.imp[1]['video']['h']).to.equal(multipleMediaRequests[1].mediaTypes.video.playerSize[1]); }); + it('should pass device.sua if present in bidderRequest fpd ortb2 object', function () { + const suaObject = {'source': 2, 'platform': {'brand': 'macOS', 'version': ['12', '4', '0']}, 'browsers': [{'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']}], 'mobile': 0, 'model': '', 'bitness': '64', 'architecture': 'x86'}; + let request = spec.buildRequests(multipleMediaRequests, { + auctionId: 'new-auction-id', + ortb2: { + device: { + sua: suaObject + } + } + }); + let data = JSON.parse(request.data); + expect(data.device.sua).to.exist.and.to.be.an('object'); + expect(data.device.sua).to.deep.equal(suaObject); + }); + it('Request params should have valid native bid request for all valid params', function () { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -3406,7 +3421,7 @@ describe('PubMatic adapter', function () { let response = spec.interpretResponse(bidResponses, request); expect(response).to.be.an('array').with.length.above(0); expect(response[0].requestId).to.equal(bidResponses.body.seatbid[0].bid[0].impid); - expect(response[0].cpm).to.equal((bidResponses.body.seatbid[0].bid[0].price).toFixed(2)); + expect(response[0].cpm).to.equal(parseFloat((bidResponses.body.seatbid[0].bid[0].price).toFixed(2))); expect(response[0].width).to.equal(bidResponses.body.seatbid[0].bid[0].w); expect(response[0].height).to.equal(bidResponses.body.seatbid[0].bid[0].h); if (bidResponses.body.seatbid[0].bid[0].crid) { @@ -3430,7 +3445,7 @@ describe('PubMatic adapter', function () { expect(response[0].partnerImpId).to.equal(bidResponses.body.seatbid[0].bid[0].id); expect(response[1].requestId).to.equal(bidResponses.body.seatbid[1].bid[0].impid); - expect(response[1].cpm).to.equal((bidResponses.body.seatbid[1].bid[0].price).toFixed(2)); + expect(response[1].cpm).to.equal(parseFloat((bidResponses.body.seatbid[1].bid[0].price).toFixed(2))); expect(response[1].width).to.equal(bidResponses.body.seatbid[1].bid[0].w); expect(response[1].height).to.equal(bidResponses.body.seatbid[1].bid[0].h); if (bidResponses.body.seatbid[1].bid[0].crid) { @@ -4063,9 +4078,64 @@ describe('PubMatic adapter', function () { expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); expect(data.imp[0]['video']['battr']).to.equal(undefined); }); + + describe('Assign Deal Tier (i.e. prebidDealPriority)', function () { + let videoSeatBid, request, newBid; + // let data = JSON.parse(request.data); + beforeEach(function () { + videoSeatBid = videoBidResponse.body.seatbid[0].bid[0]; + // const adpodValidOutstreamBidRequest = validOutstreamBidRequest.bids[0].mediaTypes.video.context = 'adpod'; + request = spec.buildRequests(bidRequests, validOutstreamBidRequest); + newBid = { + requestId: '47acc48ad47af5' + }; + videoSeatBid.ext = videoSeatBid.ext || {}; + videoSeatBid.ext.video = videoSeatBid.ext.video || {}; + // videoBidRequests[0].mediaTypes.video = videoBidRequests[0].mediaTypes.video || {}; + }); + + it('should not assign video object if deal priority is missing', function () { + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; + }); + + it('should not assign video object if context is not a adpod', function () { + videoSeatBid.ext.prebiddealpriority = 5; + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video).to.equal(undefined); + expect(newBid.video).to.not.exist; + }); + + describe('when video deal tier object is present', function () { + beforeEach(function () { + videoSeatBid.ext.prebiddealpriority = 5; + request.bidderRequest.bids[0].mediaTypes.video = { + ...request.bidderRequest.bids[0].mediaTypes.video, + context: 'adpod', + maxduration: 50 + }; + }); + + it('should set video deal tier object, when maxduration is present in ext', function () { + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(50); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); + + it('should set video deal tier object, when duration is present in ext', function () { + videoSeatBid.ext.video.duration = 20; + assignDealTier(newBid, videoSeatBid, request); + expect(newBid.video.durationSeconds).to.equal(20); + expect(newBid.video.context).to.equal('adpod'); + expect(newBid.video.dealTier).to.equal(5); + }); + }); + }); }); - describe('Marketplace params', function() { + describe('Marketplace params', function () { let sandbox, utilsMock, newBidRequests, newBidResponses; beforeEach(() => { utilsMock = sinon.mock(utils); @@ -4082,7 +4152,7 @@ describe('PubMatic adapter', function () { sandbox.restore(); }) - it('Should add bidder code as groupm for marketplace groupm response', function () { + it('Should add bidder code as groupm for marketplace groupm response ', function () { let request = spec.buildRequests(newBidRequests, { auctionId: 'new-auction-id' }); diff --git a/test/spec/modules/pubperfAnalyticsAdapter_spec.js b/test/spec/modules/pubperfAnalyticsAdapter_spec.js index 160529125d3..9949d87a2bc 100644 --- a/test/spec/modules/pubperfAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubperfAnalyticsAdapter_spec.js @@ -1,9 +1,10 @@ import pubperfAnalytics from 'modules/pubperfAnalyticsAdapter.js'; -import { expect } from 'chai'; -import { server } from 'test/mocks/xhr.js'; +import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; +import {expectEvents, fireEvents} from '../../helpers/analytics.js'; + let events = require('src/events'); let utils = require('src/utils.js'); -let constants = require('src/constants.json'); describe('Pubperf Analytics Adapter', function() { describe('Prebid Manager Analytic tests', function() { @@ -22,13 +23,7 @@ describe('Pubperf Analytics Adapter', function() { provider: 'pubperf' }); - events.emit(constants.EVENTS.AUCTION_INIT, {}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); - + fireEvents(); expect(server.requests.length).to.equal(0); expect(utils.logError.called).to.equal(true); }); @@ -42,15 +37,7 @@ describe('Pubperf Analytics Adapter', function() { provider: 'pubperf' }); - events.emit(constants.EVENTS.AUCTION_INIT, {}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); - - // 6 Pubperf events + 1 Clean.io event - sinon.assert.callCount(pubperfAnalytics.track, 7); + expectEvents().to.beTrackedBy(pubperfAnalytics.track); }); }); }); diff --git a/test/spec/modules/pubstackAnalyticsAdapter_spec.js b/test/spec/modules/pubstackAnalyticsAdapter_spec.js index 3d01a0bb506..fe7441e91e5 100644 --- a/test/spec/modules/pubstackAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubstackAnalyticsAdapter_spec.js @@ -3,17 +3,16 @@ import pubstackAnalytics from '../../../modules/pubstackAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; import * as events from 'src/events'; import constants from 'src/constants.json' +import {expectEvents} from '../../helpers/analytics.js'; describe('Pubstack Analytics Adapter', () => { const scope = utils.getWindowSelf(); - let queue = []; beforeEach(() => { - scope.PubstackAnalytics = (...args) => queue.push(args); + scope.PubstackAnalytics = sinon.stub(); adapterManager.enableAnalytics({ provider: 'pubstack' }); - queue = [] }); afterEach(() => { @@ -21,19 +20,6 @@ describe('Pubstack Analytics Adapter', () => { }); it('should forward all events to the queue', () => { - // Given - const args = 'any-args' - - // When - events.emit(constants.EVENTS.AUCTION_END, args) - events.emit(constants.EVENTS.BID_REQUESTED, args) - events.emit(constants.EVENTS.BID_ADJUSTMENT, args) - events.emit(constants.EVENTS.BID_RESPONSE, args) - events.emit(constants.EVENTS.BID_WON, args) - events.emit(constants.EVENTS.NO_BID, args) - - // Then - // 6 Pubstack events + 1 Clean.io event - expect(queue.length).to.eql(7); + expectEvents().to.beBundledTo(scope.PubstackAnalytics); }); }); diff --git a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js index dd5c223b767..e14582edc39 100644 --- a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js @@ -1,6 +1,7 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import pubwiseAnalytics from 'modules/pubwiseAnalyticsAdapter.js'; -import {server} from 'test/mocks/xhr.js'; +import {expectEvents} from '../../helpers/analytics.js'; + let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); @@ -58,23 +59,16 @@ describe('PubWise Prebid Analytics', function () { sandbox.spy(pubwiseAnalytics, 'track'); - // sent - events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); - events.emit(constants.EVENTS.AD_RENDER_FAILED, {}); - events.emit(constants.EVENTS.TCF2_ENFORCEMENT, {}); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); - - // forces flush - events.emit(constants.EVENTS.AUCTION_END, {}); - - // eslint-disable-next-line - //console.log(requests); - - /* testing for 6 calls, including the 2 we're not currently tracking */ - sandbox.assert.callCount(pubwiseAnalytics.track, 8); + expectEvents([ + constants.EVENTS.AUCTION_INIT, + constants.EVENTS.BID_REQUESTED, + constants.EVENTS.BID_RESPONSE, + constants.EVENTS.BID_WON, + constants.EVENTS.AD_RENDER_FAILED, + constants.EVENTS.TCF2_ENFORCEMENT, + constants.EVENTS.BID_TIMEOUT, + constants.EVENTS.AUCTION_END, + ]).to.beTrackedBy(pubwiseAnalytics.track); }); it('should initialize the auction properly', function () { diff --git a/test/spec/modules/pubwiseBidAdapter_spec.js b/test/spec/modules/pubwiseBidAdapter_spec.js index 1cf7a7dd280..d7b7a527485 100644 --- a/test/spec/modules/pubwiseBidAdapter_spec.js +++ b/test/spec/modules/pubwiseBidAdapter_spec.js @@ -238,7 +238,7 @@ const sampleBidderBannerRequest = { 'bidFloor': '1.00', 'currency': 'USD', 'adSlot': '', - 'adUnit': '', + 'adUnit': 'div-gpt-ad-1460505748561-0', 'bcat': [ 'IAB25-3', 'IAB26-1', @@ -535,13 +535,15 @@ describe('PubWiseAdapter', function () { describe('Handling Request Construction', function () { it('bid requests are not mutable', function() { - let sourceBidRequest = utils.deepClone(sampleValidBidRequests) - spec.buildRequests(sampleValidBidRequests, {auctinId: 'placeholder'}); + let sourceBidRequest = utils.deepClone(sampleValidBidRequests); + spec.buildRequests(sampleValidBidRequests, {auctionId: 'placeholder'}); expect(sampleValidBidRequests).to.deep.equal(sourceBidRequest, 'Should be unedited as they are used elsewhere'); }); it('should handle complex bidRequest', function() { let request = spec.buildRequests(sampleValidBidRequests, sampleBidderRequest); - expect(request.bidderRequest).to.equal(sampleBidderRequest); + expect(request.bidderRequest).to.equal(sampleBidderRequest, "Bid Request Doesn't Match Sample"); + expect(request.data.source.tid).to.equal(sampleBidderRequest.auctionId, 'AuctionId -> source.tid Mismatch'); + expect(request.data.imp[0].ext.tid).to.equal(sampleBidderRequest.bids[0].transactionId, 'TransactionId -> ext.tid Mismatch'); }); it('must conform to API for buildRequests', function() { let request = spec.buildRequests(sampleValidBidRequests); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 1ad23b9a41e..1dce87e4b8e 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -567,7 +567,9 @@ describe('pubxai analytics adapter', function() { 'host': location.host, 'path': location.pathname, 'search': location.search, - 'adUnitCount': 1 + 'adUnits': [ + '/19968336/header-bid-tag-1' + ] }, 'floorDetail': { 'fetchStatus': 'success', diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 4426c91e1ec..60dca9e6da0 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -2,7 +2,6 @@ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; import {deepClone} from 'src/utils.js'; -import { config } from 'src/config.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -225,6 +224,8 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].banner).to.not.equal(null); expect(ortbRequest.imp[1].banner.w).to.equal(728); expect(ortbRequest.imp[1].banner.h).to.equal(90); + // tmax + expect(ortbRequest.tmax).to.equal(500); }); it('Verify parse response', function () { @@ -795,4 +796,136 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[0].video.minbitrate).to.equal(200); expect(ortbRequest.imp[0].video.protocols).to.eql([1, 2, 4]); }); + it('Verify user level first party data', function () { + const bidderRequest = { + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'serialized_gpdr_data' + }, + ortb2: { + user: { + yob: 1985, + gender: 'm', + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + }; + let request = spec.buildRequests(slotConfigs, bidderRequest); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.user).to.not.equal(null); + expect(ortbRequest.user).to.deep.equal({ + yob: 1985, + gender: 'm', + ext: { + data: { + registered: true, + interests: ['cars'] + }, + consent: 'serialized_gpdr_data' + } + }); + }); + it('Verify site level first party data', function () { + const bidderRequest = { + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + }, + ortb2: { + site: { + content: { + data: [{ + name: 'www.iris.com', + ext: { + segtax: 500, + cids: ['iris_c73g5jq96mwso4d8'] + } + }] + }, + page: 'http://pub.com/news', + ref: 'http://google.com' + } + } + }; + let request = spec.buildRequests(slotConfigs, bidderRequest); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site).to.deep.equal({ + content: { + data: [{ + name: 'www.iris.com', + ext: { + segtax: 500, + cids: ['iris_c73g5jq96mwso4d8'] + } + }] + }, + page: 'https://publisher.com/home', + ref: 'https://referrer', + publisher: { + id: 'p10000' + } + }); + }); + it('Verify impression/slot level first party data', function () { + const bidderRequests = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + extra_key1: 'extra_val1', + extra_key2: 12345 + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + } + } + }]; + let request = spec.buildRequests(bidderRequests, bidderRequest); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].ext).to.not.equal(null); + expect(ortbRequest.imp[0].ext).to.deep.equal({ + prebid: { + extra_key1: 'extra_val1', + extra_key2: 12345 + }, + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + }); + }); + + it('Verify bid request timeouts', function () { + const mkRequest = (bidderRequest) => spec.buildRequests(slotConfigs, bidderRequest).data; + // assert default is used when no bidderRequest.timeout value is available + expect(mkRequest(bidderRequest).tmax).to.equal(500) + + // assert bidderRequest value is used when available + expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) + }); }); diff --git a/test/spec/modules/rasBidAdapter_spec.js b/test/spec/modules/rasBidAdapter_spec.js index 8c378aaa416..bfa72a2510e 100644 --- a/test/spec/modules/rasBidAdapter_spec.js +++ b/test/spec/modules/rasBidAdapter_spec.js @@ -59,7 +59,10 @@ describe('rasBidAdapter', function () { area: 'areatest', site: 'test', slotSequence: '0', - network: '4178463' + network: '4178463', + customParams: { + test: 'name=value' + } } }; const bid2 = { @@ -98,6 +101,7 @@ describe('rasBidAdapter', function () { expect(requests[0].url).to.have.string('euconsent=some-consent-string'); expect(requests[0].url).to.have.string('du=https%3A%2F%2Fexample.com%2F'); expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); + expect(requests[0].url).to.have.string('test=name%3Dvalue'); }); it('should return empty consent string when undefined', function () { diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index daeeb9bc47c..ced2f697649 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -4,6 +4,7 @@ import * as sinon from 'sinon'; import {default as CONSTANTS} from '../../../src/constants.json'; import * as events from '../../../src/events.js'; import 'src/prebid.js'; +import {attachRealTimeDataProvider, onDataDeletionRequest} from 'modules/rtdModule/index.js'; const getBidRequestDataSpy = sinon.spy(); @@ -305,4 +306,68 @@ describe('Real time module', function () { }); }); }); + + describe('data deletion requests', () => { + let detach = () => null; + + function mkRtdModule(name) { + const mod = { + name, + init: () => true, + onDataDeletionRequest: sinon.stub() + }; + detach = ((orig) => { + const smDetach = attachRealTimeDataProvider(mod); + return function () { + orig(); + smDetach(); + } + })(detach); + return mod; + } + let sm1, sm2, cfg1, cfg2; + beforeEach(() => { + sm1 = mkRtdModule('mockMod1'); + sm2 = mkRtdModule('mockMod2'); + cfg1 = { + name: 'mockMod1', + i: 0 + }; + cfg2 = { + name: 'mockMod2', + i: 1 + }; + rtdModule.init(config); + config.setConfig({ + realTimeData: { + dataProviders: [cfg1, cfg2], + } + }); + }); + afterEach(() => { + detach(); + config.resetConfig(); + }); + + it('calls onDataDeletionRequest on submodules', () => { + const next = sinon.stub(); + onDataDeletionRequest(next, {a: 0}); + sinon.assert.calledWith(next, {a: 0}); + sinon.assert.calledWith(sm1.onDataDeletionRequest, cfg1); + sinon.assert.calledWith(sm2.onDataDeletionRequest, cfg2); + }); + + describe('does not choke if onDataDeletionRequest', () => { + Object.entries({ + 'is missing': () => { delete sm1.onDataDeletionRequest }, + 'throws': () => { sm1.onDataDeletionRequest.throws(new Error()) } + }).forEach(([t, setup]) => { + it(t, () => { + setup(); + onDataDeletionRequest(sinon.stub()); + sinon.assert.calledWith(sm2.onDataDeletionRequest, cfg2); + }) + }) + }) + }); }); diff --git a/test/spec/modules/redtramBidAdapter_spec.js b/test/spec/modules/redtramBidAdapter_spec.js new file mode 100644 index 00000000000..e136c37962b --- /dev/null +++ b/test/spec/modules/redtramBidAdapter_spec.js @@ -0,0 +1,256 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/redtramBidAdapter.js'; +import { BANNER } from '../../../src/mediaTypes.js'; +import * as utils from '../../../src/utils.js'; + +describe('RedtramBidAdapter', function () { + const bid = { + bidId: '23dc19818e5293', + bidder: 'redtram', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 23611, + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://prebid.redtram.com/pbjs'); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'schain', 'bidfloor'); + expect(placement.placementId).to.equal(23611); + expect(placement.bidId).to.equal('23dc19818e5293'); + expect(placement.adFormat).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23dc19818e5293', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: {} + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23dc19818e5293'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23dc19818e5293', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + it('should do nothing on getUserSyncs', function () { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://prebid.redtram.com/sync/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + }); + + describe('on bidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('should replace nurl for banner', function () { + const nurl = 'nurl/?ap=${' + 'AUCTION_PRICE}'; + const bid = { + 'bidderCode': 'redtram', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '5691dd18ba6ab6', + 'requestId': '23dc19818e5293', + 'transactionId': '948c716b-bf64-4303-bcf4-395c2f6a9770', + 'auctionId': 'a6b7c61f-15a9-481b-8f64-e859787e9c07', + 'mediaType': 'banner', + 'source': 'client', + 'ad': "
\n", + 'cpm': 0.68, + 'nurl': nurl, + 'creativeId': 'test', + 'currency': 'USD', + 'dealId': '', + 'meta': { + 'advertiserDomains': [], + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'name': 'redtram' + } + ] + } + }, + 'netRevenue': true, + 'ttl': 120, + 'metrics': {}, + 'adapterCode': 'redtram', + 'originalCpm': 0.68, + 'originalCurrency': 'USD', + 'responseTimestamp': 1668162732297, + 'requestTimestamp': 1668162732292, + 'bidder': 'redtram', + 'adUnitCode': 'div-prebid', + 'timeToRespond': 5, + 'pbLg': '0.50', + 'pbMg': '0.60', + 'pbHg': '0.68', + 'pbAg': '0.65', + 'pbDg': '0.68', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'redtram', + 'hb_adid': '5691dd18ba6ab6', + 'hb_pb': '0.68', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': '' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 23611 + } + ] + }; + spec.onBidWon(bid); + expect(bid.nurl).to.deep.equal('nurl/?ap=0.68'); + }); + }); +}); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 7daedb9015a..4fa4ff354ec 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -106,6 +106,9 @@ describe('riseAdapter', function () { bidderCode: 'rise', } const placementId = '12345678'; + const api = [1, 2]; + const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; + const protocols = [2, 3, 5, 6]; it('sends the placementId to ENDPOINT via POST', function () { bidRequests[0].params.placementId = placementId; @@ -113,6 +116,13 @@ describe('riseAdapter', function () { expect(request.data.bids[0].placementId).to.equal(placementId); }); + it('sends the is_wrapper parameter to ENDPOINT via POST', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('is_wrapper'); + expect(request.data.params.is_wrapper).to.equal(false); + }); + it('sends bid request to ENDPOINT via POST', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.url).to.equal(ENDPOINT); @@ -144,6 +154,27 @@ describe('riseAdapter', function () { expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); }); + it('should send the correct supported api array', function () { + bidRequests[0].mediaTypes.video.api = api; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].api).to.be.an('array'); + expect(request.data.bids[0].api).to.eql([1, 2]); + }); + + it('should send the correct mimes array', function () { + bidRequests[1].mediaTypes.banner.mimes = mimes; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[1].mimes).to.be.an('array'); + expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + }); + + it('should send the correct protocols array', function () { + bidRequests[0].mediaTypes.video.protocols = protocols; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].protocols).to.be.an('array'); + expect(request.data.bids[0].protocols).to.eql([2, 3, 5, 6]); + }); + it('should send the correct sizes array', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].sizes).to.be.an('array'); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index f4bcb48474a..6d41df7605b 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; describe('RTBHouseAdapter', () => { const adapter = newBidder(spec); @@ -97,6 +98,10 @@ describe('RTBHouseAdapter', () => { ]; }); + afterEach(function () { + config.resetConfig(); + }); + it('should build test param into the request', () => { let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; expect(JSON.parse(builtTestRequest).test).to.equal(1); @@ -263,6 +268,45 @@ describe('RTBHouseAdapter', () => { expect(data.source).to.not.have.property('ext'); }); + context('FLEDGE', function() { + afterEach(function () { + config.resetConfig(); + }); + + it('sends bid request to FLEDGE ENDPOINT via POST', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + config.setConfig({ fledgeConfig: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebidfledge/bids'); + expect(request.method).to.equal('POST'); + }); + + it('when FLEDGE is disabled, should not send imp.ext.ae', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + bidRequest[0].ortb2Imp = { + ext: { ae: 2 } + }; + const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: false }); + let data = JSON.parse(request.data); + if (data.imp[0].ext) { + expect(data.imp[0].ext).to.not.have.property('ae'); + } + }); + + it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + bidRequest[0].ortb2Imp = { + ext: { ae: 2 } + }; + const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + let data = JSON.parse(request.data); + expect(data.imp[0].ext.ae).to.equal(2); + }); + }); + describe('native imp', () => { function basicRequest(extension) { return Object.assign({ @@ -460,6 +504,29 @@ describe('RTBHouseAdapter', () => { 'h': 250 }]; + let fledgeResponse = { + 'id': 'bid-identifier', + 'ext': { + 'igbid': [{ + 'impid': 'test-bid-id', + 'igbuyer': [{ + 'igdomain': 'https://buyer-domain.com', + 'buyersignal': {} + }] + }], + 'sellerTimeout': 500, + 'seller': 'https://seller-domain.com', + 'decisionLogicUrl': 'https://seller-domain.com/decision-logic.js' + }, + 'bidid': 'bid-identifier', + 'seatbid': [{ + 'bid': [{ + 'id': 'bid-response-id', + 'impid': 'test-bid-id' + }] + }] + }; + it('should get correct bid response', function () { let expectedResponse = [ { @@ -488,6 +555,18 @@ describe('RTBHouseAdapter', () => { expect(result.length).to.equal(0); }); + context('when the response contains FLEDGE interest groups config', function () { + let bidderRequest; + let response = spec.interpretResponse({body: fledgeResponse}, {bidderRequest}); + + it('should return FLEDGE auction_configs alongside bids', function () { + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs.length).to.equal(1); + expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); + }); + }); + describe('native', () => { const adm = { native: { diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index fdbb8235f5f..05b6cc6b6f4 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -4,6 +4,7 @@ import rubiconAnalyticsAdapter, { getHostNameFromReferer, storage, rubiConf, + resetRubiConf } from 'modules/rubiconAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -667,6 +668,7 @@ describe('rubicon analytics adapter', function () { expect(utils.generateUUID.called).to.equal(true); }); it('should merge in and preserve older set configs', function () { + resetRubiConf(); config.setConfig({ rubicon: { wrapperName: '1001_general', @@ -689,7 +691,6 @@ describe('rubicon analytics adapter', function () { fpkvs: { source: 'fb' }, - updatePageView: true }); // update it with stuff @@ -714,7 +715,6 @@ describe('rubicon analytics adapter', function () { source: 'fb', link: 'email' }, - updatePageView: true }); // overwriting specific edge keys should update them @@ -740,7 +740,6 @@ describe('rubicon analytics adapter', function () { link: 'iMessage', source: 'twitter' }, - updatePageView: true }); }); }); diff --git a/test/spec/modules/scatteredBidAdapter_spec.js b/test/spec/modules/scatteredBidAdapter_spec.js new file mode 100644 index 00000000000..7f0b13bc07b --- /dev/null +++ b/test/spec/modules/scatteredBidAdapter_spec.js @@ -0,0 +1,210 @@ +import { spec, converter } from 'modules/scatteredBidAdapter.js'; +import { assert } from 'chai'; +import { config } from 'src/config.js'; +import { deepClone, mergeDeep } from '../../../src/utils'; +describe('Scattered adapter', function () { + describe('isBidRequestValid', function () { + // A valid bid + let validBid = { + bidder: 'scattered', + mediaTypes: { + banner: { + sizes: [[300, 250], [760, 400]] + } + }, + params: { + bidderDomain: 'https://prebid.scattered.eu', + test: 0 + } + }; + + // Because this valid bid is modified to create invalid bids in following tests we first check it. + // We must be sure it is a valid one, not to get false negatives. + it('should accept a valid bid', function () { + assert.isTrue(spec.isBidRequestValid(validBid)); + }); + + it('should skip if bidderDomain info is missing', function () { + let bid = deepClone(validBid); + + delete bid.params.bidderDomain; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + + it('should expect at least one banner size', function () { + let bid = deepClone(validBid); + + delete bid.mediaTypes.banner; + assert.isFalse(spec.isBidRequestValid(bid)); + + // empty sizes array + bid.mediaTypes = { + banner: { + sizes: [] + } + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', function () { + let arrayOfValidBidRequests, validBidderRequest; + + beforeEach(function () { + arrayOfValidBidRequests = [{ + bidder: 'scattered', + params: { + bidderDomain: 'https://prebid.scattered.eu', + test: 0 + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [760, 400]] + }, + adUnitCode: 'test-div', + transactionId: '32d09c47-c6b8-40b0-9605-2e251a472ea4', + bidId: '21adc5d8765aa1', + bidderRequestId: '130728f7662afc', + auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32', + }, + }]; + + validBidderRequest = { + bidderCode: 'scattered', + auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32', + gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', gdprApplies: true }, + refererInfo: { + domain: 'localhost', + page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true', + }, + ortb2: { + site: { + publisher: { + name: 'publisher1 INC.' + } + } + } + }; + }); + + it('should validate request format', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + assert.equal(request.method, 'POST'); + assert.deepEqual(request.options, { contentType: 'application/json' }); + assert.ok(request.data); + }); + + it('has the right fields filled', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + const bidderRequest = request.data; + assert.equal(bidderRequest.id, validBidderRequest.auctionId); + assert.ok(bidderRequest.site); + assert.ok(bidderRequest.source); + assert.lengthOf(bidderRequest.imp, 1); + }); + + it('should configure the site object', function () { + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + const site = request.data.site; + assert.equal(site.publisher.name, validBidderRequest.ortb2.site.publisher.name) + }); + + it('should configure site with ortb2', function () { + const req = mergeDeep({}, validBidderRequest, { + ortb2: { + site: { + id: '876', + publisher: { + domain: 'publisher1.eu' + } + } + } + }); + + let request = spec.buildRequests(arrayOfValidBidRequests, req); + const site = request.data.site; + assert.deepEqual(site, { + id: '876', + publisher: { + domain: 'publisher1.eu', + name: 'publisher1 INC.' + } + }); + }); + + it('should send device info', function () { + it('should send info about device', function () { + config.setConfig({ + device: { w: 375, h: 273 } + }); + + let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 375); + assert.equal(request.device.h, 273); + }); + }) + }) +}) + +describe('interpretResponse', function () { + const serverResponse = { + body: { + id: 'b4a45a23-8371-4d87-9308-39146b29ca32', + bidid: '11111111-2222-2222-2222-333333333333', + cur: 'PLN', + seatbid: [{ + bid: [ + { + id: '234234-234234-234234', // bidder generated + impid: '123', + price: '34.2', + nurl: 'https://scattered.eu/nurl', + adm: '
', + cpm: '34.2', + creativeId: '2345-2345-23', + currency: 'PLN', + height: 456, + width: 345, + mediaType: 'banner', + requestId: '123', + ttl: 360, + }; + expect(results.length).to.equal(1); + sinon.assert.match(results[0], expected); + }); +}); diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 81af9546ff0..6086db01de4 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -38,7 +38,7 @@ describe('SeedingAlliance adapter', function () { }); it('should have default request structure', function () { - let keys = 'site,device,cur,imp,user,regs'.split(','); + let keys = 'site,cur,imp,regs'.split(','); let validBidRequests = [{ bidId: 'bidId', params: {} @@ -60,14 +60,17 @@ describe('SeedingAlliance adapter', function () { assert.equal(request.id, validBidRequests[0].auctionId); }); - it('Verify the device', function () { + it('Verify the site url', function () { + let siteUrl = 'https://www.yourdomain.tld/your-directory/'; let validBidRequests = [{ bidId: 'bidId', - params: {} + params: { + url: siteUrl + } }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); - assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.site.page, siteUrl); }); it('Verify native asset ids', function () { @@ -109,7 +112,7 @@ describe('SeedingAlliance adapter', function () { }); describe('interpretResponse', function () { - const goodResponse = { + const goodNativeResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', @@ -136,51 +139,91 @@ describe('SeedingAlliance adapter', function () { ] } }; + + const goodBannerResponse = { + body: { + cur: 'EUR', + id: 'b4516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [ + { + seat: 'seedingAlliance', + bid: [{ + adm: '', + impid: 1, + price: 0.90, + h: 250, + w: 300 + }] + } + ] + } + }; + const badResponse = { body: { cur: 'EUR', id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', seatbid: [] }}; - const bidRequest = { + const bidNativeRequest = { data: {}, - bids: [{ bidId: 'bidId1' }] + bidRequests: [{bidId: 'bidId1', nativeParams: {title: {required: true, len: 800}}}] + }; + + const bidBannerRequest = { + data: {}, + bidRequests: [{bidId: 'bidId1', sizes: [300, 250]}] }; it('should return null if body is missing or empty', function () { - const result = spec.interpretResponse(badResponse, bidRequest); + const result = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); delete badResponse.body - const result1 = spec.interpretResponse(badResponse, bidRequest); + const result1 = spec.interpretResponse(badResponse, bidNativeRequest); assert.equal(result.length, 0); }); it('should return the correct params', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; - - assert.deepEqual(result[0].currency, goodResponse.body.cur); - assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); - assert.deepEqual(result[0].cpm, bid.price); - assert.deepEqual(result[0].creativeId, bid.crid); - assert.deepEqual(result[0].mediaType, 'native'); - assert.deepEqual(result[0].bidderCode, 'seedingAlliance'); + const resultNative = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bidNative = goodNativeResponse.body.seatbid[0].bid[0]; + + assert.deepEqual(resultNative[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultNative[0].currency, goodNativeResponse.body.cur); + assert.deepEqual(resultNative[0].requestId, bidNativeRequest.bidRequests[0].bidId); + assert.deepEqual(resultNative[0].cpm, bidNative.price); + assert.deepEqual(resultNative[0].creativeId, bidNative.crid); + assert.deepEqual(resultNative[0].mediaType, 'native'); + + const resultBanner = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + + assert.deepEqual(resultBanner[0].bidderCode, 'seedingAlliance'); + assert.deepEqual(resultBanner[0].mediaType, 'banner'); + assert.deepEqual(resultBanner[0].width, bidBannerRequest.bidRequests[0].sizes[0]); + assert.deepEqual(resultBanner[0].height, bidBannerRequest.bidRequests[0].sizes[1]); }); - it('should return the correct tracking links', function () { - const result = spec.interpretResponse(goodResponse, bidRequest); - const bid = goodResponse.body.seatbid[0].bid[0]; + it('should return the correct native tracking links', function () { + const result = spec.interpretResponse(goodNativeResponse, bidNativeRequest); + const bid = goodNativeResponse.body.seatbid[0].bid[0]; const regExpPrice = new RegExp('price=' + bid.price); result[0].native.clickTrackers.forEach(function (clickTracker) { - assert.ok(clickTracker.search(regExpPrice) > -1); + assert.ok(clickTracker.search(regExpPrice) > -1); }); result[0].native.impressionTrackers.forEach(function (impTracker) { - assert.ok(impTracker.search(regExpPrice) > -1); + assert.ok(impTracker.search(regExpPrice) > -1); }); }); + + it('should return the correct banner content', function () { + const result = spec.interpretResponse(goodBannerResponse, bidBannerRequest); + const bid = goodBannerResponse.body.seatbid[0].bid[0]; + const regExpContent = new RegExp(''); + + assert.ok(result[0].ad.search(regExpContent) > -1); + }); }); }); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 05d609e8003..3627296975b 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js'; import * as utils from 'src/utils.js'; +import { config } from '../../../src/config.js'; const PUBLISHER_ID = '0000-0000-01'; const ADUNIT_ID = '000000'; @@ -24,81 +25,118 @@ function getSlotConfigs(mediaTypes, params) { }; } -function createVideoSlotConfig(mediaType) { +function createInStreamSlotConfig(mediaType) { return getSlotConfigs(mediaType, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'video', + placement: 'inStream', }); } +const createBannerSlotConfig = (placement, mediatypes) => { + return getSlotConfigs(mediatypes || { banner: {} }, { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement, + }); +}; + describe('Seedtag Adapter', function () { describe('isBidRequestValid method', function () { describe('returns true', function () { describe('when banner slot config has all mandatory params', () => { - describe('and placement has the correct value', function () { - const createBannerSlotConfig = (placement) => { - return getSlotConfigs( - { banner: {} }, - { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement, - } + const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; + placements.forEach((placement) => { + it(placement + 'should be valid', function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement) ); - }; - const placements = [ - 'banner', - 'video', - 'inImage', - 'inScreen', - 'inArticle', - ]; - placements.forEach((placement) => { - it('should be ' + placement, function () { + expect(isBidRequestValid).to.equal(true); + }); + + it( + placement + + ' should be valid when has display and video mediatypes, and video context is outstream', + function () { const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement) + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }) ); expect(isBidRequestValid).to.equal(true); - }); - }); + } + ); + + it( + placement + + " shouldn't be valid when has display and video mediatypes, and video context is instream", + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement, { + banner: {}, + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(false); + } + ); }); }); describe('when video slot has all mandatory params', function () { it('should return true, when video context is instream', function () { - const slotConfig = getSlotConfigs( - { - video: { - context: 'instream', - playerSize: [[600, 200]], - }, + const slotConfig = createInStreamSlotConfig({ + video: { + context: 'instream', + playerSize: [[600, 200]], }, - { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement: 'video', - } - ); + }); const isBidRequestValid = spec.isBidRequestValid(slotConfig); expect(isBidRequestValid).to.equal(true); }); - - it('should return true, when video context is outstream', function () { + it('should return true, when video context is instream and mediatype is video and banner', function () { + const slotConfig = createInStreamSlotConfig({ + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + banner: {}, + }); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(true); + }); + it('should return false, when video context is instream, but placement is not inStream', function () { const slotConfig = getSlotConfigs( { video: { - context: 'outstream', + context: 'instream', playerSize: [[600, 200]], }, }, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'video', + placement: 'inBanner', } ); const isBidRequestValid = spec.isBidRequestValid(slotConfig); - expect(isBidRequestValid).to.equal(true); + expect(isBidRequestValid).to.equal(false); + }); + it('should return false, when video context is outstream', function () { + const slotConfig = createInStreamSlotConfig({ + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }); + const isBidRequestValid = spec.isBidRequestValid(slotConfig); + expect(isBidRequestValid).to.equal(false); }); }); }); @@ -111,7 +149,7 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ adUnitId: ADUNIT_ID, - placement: 'banner', + placement: 'inBanner', }) ); expect(isBidRequestValid).to.equal(false); @@ -120,7 +158,7 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ publisherId: PUBLISHER_ID, - placement: 'banner', + placement: 'inBanner', }) ); expect(isBidRequestValid).to.equal(false); @@ -145,47 +183,41 @@ describe('Seedtag Adapter', function () { expect(isBidRequestValid).to.equal(false); }); }); + describe('when video mediaType object is not correct', function () { - function createVideoSlotconfig(mediaType) { - return getSlotConfigs(mediaType, { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement: 'video', - }); - } it('is a void object', function () { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotConfig({ video: {} }) + createInStreamSlotConfig({ video: {} }) ); expect(isBidRequestValid).to.equal(false); }); it('does not have playerSize.', function () { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotConfig({ video: { context: 'instream' } }) + createInStreamSlotConfig({ video: { context: 'instream' } }) ); expect(isBidRequestValid).to.equal(false); }); it('is outstream ', function () { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotConfig({ + createInStreamSlotConfig({ video: { context: 'outstream', playerSize: [[600, 200]], }, }) ); - expect(isBidRequestValid).to.equal(true); + expect(isBidRequestValid).to.equal(false); }); describe('order does not matter', function () { it('when video is not the first slot', function () { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotConfig({ banner: {}, video: {} }) + createInStreamSlotConfig({ banner: {}, video: {} }) ); expect(isBidRequestValid).to.equal(false); }); it('when video is the first slot', function () { const isBidRequestValid = spec.isBidRequestValid( - createVideoSlotConfig({ video: {}, banner: {} }) + createInStreamSlotConfig({ video: {}, banner: {} }) ); expect(isBidRequestValid).to.equal(false); }); @@ -199,21 +231,27 @@ describe('Seedtag Adapter', function () { refererInfo: { page: 'referer' }, timeout: 1000, }; - const mandatoryParams = { + const mandatoryDisplayParams = { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'banner', + placement: 'inBanner', + }; + const mandatoryVideoParams = { + publisherId: PUBLISHER_ID, + adUnitId: ADUNIT_ID, + placement: 'inStream', }; - const inStreamParams = Object.assign({}, mandatoryParams, { - video: { - mimes: 'mp4', - }, - }); const validBidRequests = [ - getSlotConfigs({ banner: {} }, mandatoryParams), + getSlotConfigs({ banner: {} }, mandatoryDisplayParams), getSlotConfigs( - { video: { context: 'instream', playerSize: [[300, 200]] } }, - inStreamParams + { + video: { + context: 'instream', + playerSize: [[300, 200]], + mimes: ['video/mp4'], + }, + }, + mandatoryVideoParams ), ]; it('Url params should be correct ', function () { @@ -238,26 +276,6 @@ describe('Seedtag Adapter', function () { expect(data.bidRequests[0].adUnitCode).to.equal('adunit-code'); }); - describe('adPosition param', function () { - it('should sended when publisher set adPosition param', function () { - const params = Object.assign({}, mandatoryParams, { - adPosition: 1, - }); - const validBidRequests = [getSlotConfigs({ banner: {} }, params)]; - const request = spec.buildRequests(validBidRequests, bidderRequest); - const data = JSON.parse(request.data); - expect(data.bidRequests[0].adPosition).to.equal(1); - }); - it('should not sended when publisher has not set adPosition param', function () { - const validBidRequests = [ - getSlotConfigs({ banner: {} }, mandatoryParams), - ]; - const request = spec.buildRequests(validBidRequests, bidderRequest); - const data = JSON.parse(request.data); - expect(data.bidRequests[0].adPosition).to.equal(undefined); - }); - }); - describe('GDPR params', function () { describe('when there arent consent management platform', function () { it('cmp should be false', function () { @@ -309,10 +327,7 @@ describe('Seedtag Adapter', function () { bidderRequest['uspConsent'] = uspConsent; - const request = spec.buildRequests( - validBidRequests, - bidderRequest - ); + const request = spec.buildRequests(validBidRequests, bidderRequest); const payload = JSON.parse(request.data); expect(payload.uspConsent).to.not.exist; @@ -346,7 +361,7 @@ describe('Seedtag Adapter', function () { ); expect(videoBid.supplyTypes[0]).to.equal('video'); expect(videoBid.adUnitId).to.equal('000000'); - expect(videoBid.videoParams.mimes).to.equal('mp4'); + expect(videoBid.videoParams.mimes).to.eql(['video/mp4']); expect(videoBid.videoParams.w).to.equal(300); expect(videoBid.videoParams.h).to.equal(200); expect(videoBid.sizes[0][0]).to.equal(300); @@ -357,6 +372,29 @@ describe('Seedtag Adapter', function () { }); }); + describe('COPPA param', function () { + it('should add COPPA param to payload when prebid config has parameter COPPA equal to true', function () { + config.setConfig({ coppa: true }); + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.coppa).to.equal(true); + }); + + it('should not add COPPA param to payload when prebid config has parameter COPPA equal to false', function () { + config.setConfig({ coppa: false }); + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.coppa).to.be.undefined; + }); + + it('should not add COPPA param to payload when prebid config has not parameter COPPA', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.coppa).to.be.undefined; + }); + }); describe('schain param', function () { it('should add schain to payload when exposed on validBidRequest', function () { // https://github.com/prebid/Prebid.js/blob/master/modules/schain.md#sample-code-for-passing-the-schain-object @@ -398,6 +436,52 @@ describe('Seedtag Adapter', function () { expect(payload.schain).to.not.exist; }); }); + + describe('GPP param', function () { + it('should be added to payload when bidderRequest has gppConsent param', function () { + const gppConsent = { + gppString: 'someGppString', + applicableSections: [7] + } + bidderRequest['gppConsent'] = gppConsent + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.exist; + expect(data.gppConsent.gppString).to.equal(gppConsent.gppString); + expect(data.gppConsent.applicableSections[0]).to.equal(gppConsent.applicableSections[0]); + }); + + it('should be undefined on payload when bidderRequest has not gppConsent param', function () { + bidderRequest.gppConsent = undefined + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.be.undefined; + }); + + it('should be added to payload when bidderRequest has ortb2 param', function () { + const ortb2 = { + regs: { + gpp: 'someGppString', + gpp_sid: [7] + } + } + bidderRequest['gppConsent'] = undefined + bidderRequest['ortb2'] = ortb2; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.exist; + expect(data.gppConsent.gppString).to.equal(ortb2.regs.gpp); + expect(data.gppConsent.applicableSections[0]).to.equal(ortb2.regs.gpp_sid[0]); + }); + + it('should be added to payload when bidderRequest has neither gppConsent nor ortb2', function () { + bidderRequest['ortb2'] = undefined; + bidderRequest['gppConsent'] = undefined; + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.gppConsent).to.be.undefined; + }); + }); }); describe('interpret response method', function () { @@ -536,11 +620,11 @@ describe('Seedtag Adapter', function () { const timeoutUrl = getTimeoutUrl(timeoutData); expect(timeoutUrl).to.equal( 'https://s.seedtag.com/se/hb/timeout?publisherToken=' + - params.publisherId + - '&adUnitId=' + - params.adUnitId + - '&timeout=' + - timeout + params.publisherId + + '&adUnitId=' + + params.adUnitId + + '&timeout=' + + timeout ); }); @@ -552,11 +636,11 @@ describe('Seedtag Adapter', function () { expect( utils.triggerPixel.calledWith( 'https://s.seedtag.com/se/hb/timeout?publisherToken=' + - params.publisherId + - '&adUnitId=' + - params.adUnitId + - '&timeout=' + - timeout + params.publisherId + + '&adUnitId=' + + params.adUnitId + + '&timeout=' + + timeout ) ).to.equal(true); }); diff --git a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js index 6d772de02eb..6cdc3c448b9 100644 --- a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js +++ b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js @@ -1,5 +1,7 @@ import sigmoidAnalytic from 'modules/sigmoidAnalyticsAdapter.js'; -import { expect } from 'chai'; +import {expect} from 'chai'; +import {expectEvents} from '../../helpers/analytics.js'; + let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); @@ -32,13 +34,7 @@ describe('sigmoid Prebid Analytic', function () { } }); - events.emit(constants.EVENTS.AUCTION_INIT, {}); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); - - sinon.assert.callCount(sigmoidAnalytic.track, 8); + expectEvents().to.beTrackedBy(sigmoidAnalytic.track); }); }); describe('build utm tag data', function () { diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js index 9cf392ebd62..eccc4777906 100644 --- a/test/spec/modules/sirdataRtdProvider_spec.js +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -1,17 +1,17 @@ -import { addSegmentData, getSegmentsAndCategories, sirdataSubmodule } from 'modules/sirdataRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; +import {addSegmentData, getSegmentsAndCategories, sirdataSubmodule} from 'modules/sirdataRtdProvider.js'; +import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; -describe('sirdataRtdProvider', function() { - describe('sirdataSubmodule', function() { +describe('sirdataRtdProvider', function () { + describe('sirdataSubmodule', function () { it('successfully instantiates', function () { - expect(sirdataSubmodule.init()).to.equal(true); + expect(sirdataSubmodule.init()).to.equal(true); }); }); - describe('Add Segment Data', function() { - it('adds segment data', function() { + describe('Add Segment Data', function () { + it('adds segment data', function () { const config = { params: { setGptKeyValues: false, @@ -42,23 +42,34 @@ describe('sirdataRtdProvider', function() { contextual_categories: {'333333': 100} }; - addSegmentData({adUnits}, data, config, () => {}); + addSegmentData({adUnits}, data, config, () => { + }); expect(adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); - expect(adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); - expect(adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); }); }); - describe('Get Segments And Categories', function() { - it('gets data from async request and adds segment data', function() { + describe('Get Segments And Categories', function () { + it('gets data from async request and adds segment data', function () { + const overrideAppnexus = function (adUnit, list, data, bid) { + deepSetValue(bid, 'params.keywords.custom', list); + } + const config = { params: { setGptKeyValues: false, contextualMinRelevancyScore: 50, bidders: [{ - bidder: 'appnexus' + bidder: 'appnexus', + customFunction: overrideAppnexus }, { - bidder: 'other' + bidder: 'smartadserver' + }, { + bidder: 'ix', + sizeLimit: 1200, + }, { + bidder: 'rubicon', + }, { + bidder: 'proxistore', }] } }; @@ -71,24 +82,107 @@ describe('sirdataRtdProvider', function() { placementId: 13144370 } }, { - bidder: 'other' + bidder: 'smartadserver', + params: { + siteId: 207435, + pageId: 896536, + formatId: 62913 + } + }, { + bidder: 'proxistore', + params: {website: 'demo.sirdata.com', language: 'fr'}, + adUnitCode: 'HALFPAGE_CENTER_LOADER', + transactionId: '92ac333a-a569-4827-abf1-01fc9d19278a', + sizes: [[300, 600]], + mediaTypes: { + banner: { + filteredSizeConfig: [ + {minViewPort: [1600, 0], sizes: [[300, 600]]}, + ], + sizeConfig: [ + {minViewPort: [0, 0], sizes: [[300, 600]]}, + {minViewPort: [768, 0], sizes: [[300, 600]]}, + {minViewPort: [1200, 0], sizes: [[300, 600]]}, + {minViewPort: [1600, 0], sizes: [[300, 600]]}, + ], + sizes: [[300, 600]], + }, + }, + bidId: '190bab495bc5f6e', + bidderRequestId: '18c0b0f0c91cd88', + auctionId: '9bdd917b-908d-4d9f-8f2f-d443277a62fc', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, { + bidder: 'ix', + params: { + siteId: '12345', + size: [300, 600] + } + }, { + bidder: 'rubicon', + params: { + accountId: 14062, + siteId: 70608, + zoneId: 498816 + } }] - }] + }], + ortb2Fragments: { + global: {} + } }; let data = { - segments: [111111, 222222], - contextual_categories: {'333333': 100} + 'segments': [111111, 222222], + 'segtaxid': null, + 'cattaxid': null, + 'contextual_categories': {'333333': 100}, + 'shared_taxonomy': { + '27440': { + 'segments': [444444, 555555], + 'segtaxid': 552, + 'cattaxid': 553, + 'contextual_categories': {'666666': 100} + } + }, + 'global_taxonomy': { + '9998': { + 'segments': [123, 234], + 'segtaxid': 4, + 'cattaxid': 7, + 'contextual_categories': {'345': 100, '456': 100} + } + } }; - getSegmentsAndCategories(reqBidsConfigObj, () => {}, config, {}); + getSegmentsAndCategories(reqBidsConfigObj, () => { + }, config, {}); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(data)); - expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']); - expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']); - expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']); + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.have.deep.property('target', 'sd_rtd=111111;sd_rtd=222222;sd_rtd=333333;sd_rtd=444444;sd_rtd=555555;sd_rtd=666666'); + + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].name).to.equal( + 'sirdata.com' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment).to.eql([ + {id: '345'}, + {id: '456'} + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(7); + + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].name).to.equal( + 'sirdata.com' + ); + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].segment).to.eql([ + {id: '123'}, + {id: '234'} + ]); + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); }); }); }); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 99592765845..c7dbf1363e7 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -6,6 +6,7 @@ import {createEidsArray} from 'modules/userId/eids.js'; const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; +const ADTYPE_NATIVE = 'Native'; const REFERRER = 'http://example.com/page.html' const CONSENT_STRING = 'HFIDUYFIUYIUYWIPOI87392DSU' @@ -421,7 +422,6 @@ describe('smaatoBidAdapterTest', () => { }, adUnitCode: '/19968336/header-bid-tag-0', transactionId: 'transactionId', - sizes: [[300, 50]], bidId: 'bidId', bidderRequestId: 'bidderRequestId', src: 'client', @@ -793,6 +793,168 @@ describe('smaatoBidAdapterTest', () => { }); }); + describe('buildRequests for native imps', () => { + const NATIVE_OPENRTB_REQUEST = { + ver: '1.2', + assets: [ + { + id: 4, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }, + { + id: 2, + required: 1, + img: { + type: 2, + w: 50, + h: 50 + } + }, + { + id: 0, + required: 1, + title: { + len: 80 + } + }, + { + id: 1, + required: 1, + data: { + type: 1 + } + }, + { + id: 3, + required: 1, + data: { + type: 2 + } + }, + { + id: 5, + required: 1, + data: { + type: 3 + } + }, + { + id: 6, + required: 1, + data: { + type: 4 + } + }, + { + id: 7, + required: 1, + data: { + type: 5 + } + }, + { + id: 8, + required: 1, + data: { + type: 6 + } + }, + { + id: 9, + required: 1, + data: { + type: 7 + } + }, + { + id: 10, + required: 0, + data: { + type: 8 + } + }, + { + id: 11, + required: 1, + data: { + type: 9 + } + }, + { + id: 12, + require: 0, + data: { + type: 10 + } + }, + { + id: 13, + required: 0, + data: { + type: 11 + } + }, + { + id: 14, + required: 1, + data: { + type: 12 + } + } + ] + }; + + const singleNativeBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + nativeOrtbRequest: NATIVE_OPENRTB_REQUEST, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + + it('sends correct native imps', () => { + const reqs = spec.buildRequests([singleNativeBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].id).to.be.equal('bidId'); + expect(req.imp[0].tagid).to.be.equal('adspaceId'); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].native.request).to.deep.equal(JSON.stringify(NATIVE_OPENRTB_REQUEST)); + }); + + it('sends bidfloor when configured', () => { + const singleNativeBidRequestWithFloor = Object.assign({}, singleNativeBidRequest); + singleNativeBidRequestWithFloor.getFloor = function(arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'native' && + JSON.stringify(arg.size) === JSON.stringify([150, 50])) { + return { + currency: 'USD', + floor: 0.123 + } + } + } + const reqs = spec.buildRequests([singleNativeBidRequestWithFloor], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.123); + }); + }); + describe('in-app requests', () => { const LOCATION = { lat: 33.3, @@ -932,50 +1094,131 @@ describe('smaatoBidAdapterTest', () => { } } + const NATIVE_RESPONSE = { + ver: '1.2', + link: { + url: 'https://link.url', + clicktrackers: [ + 'http://click.url/v1/click?e=prebid' + ] + }, + assets: [ + { + id: 0, + required: 1, + title: { + text: 'Title' + } + }, + { + id: 2, + required: 1, + img: { + type: 1, + url: 'https://logo.png', + w: 40, + h: 40 + } + }, + { + id: 4, + required: 1, + img: { + type: 3, + url: 'https://main.png', + w: 480, + h: 320 + } + }, + { + id: 3, + required: 1, + data: { + type: 2, + value: 'Desc' + } + }, + { + id: 14, + required: 1, + data: { + type: 12, + value: 'CTAText' + } + }, + { + id: 5, + required: 0, + data: { + type: 3, + value: '2 stars' + } + } + ], + eventtrackers: [ + { + event: 2, + method: 1, + url: 'https://js.url' + }, + { + event: 1, + method: 1, + url: 'http://view.url/v1/view?e=prebid' + } + ], + privacy: 'https://privacy.com/' + } + const buildOpenRtbBidResponse = (adType) => { let adm = ''; switch (adType) { case ADTYPE_IMG: - adm = JSON.stringify({ - image: { - img: { - url: 'https://prebid/static/ad.jpg', - w: 320, - h: 50, - ctaurl: 'https://prebid/track/ctaurl' - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } - }); + adm = JSON.stringify( + { + image: { + img: { + url: 'https://prebid/static/ad.jpg', + w: 320, + h: 50, + ctaurl: 'https://prebid/track/ctaurl' + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } + }); break; case ADTYPE_RICHMEDIA: - adm = JSON.stringify({ - richmedia: { - mediadata: { - content: '

RICHMEDIA CONTENT

', - w: 800, - h: 600 - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } - }); + adm = JSON.stringify( + { + richmedia: { + mediadata: { + content: '

RICHMEDIA CONTENT

', + w: 800, + h: 600 + }, + impressiontrackers: [ + 'https://prebid/track/imp/1', + 'https://prebid/track/imp/2' + ], + clicktrackers: [ + 'https://prebid/track/click/1' + ] + } + }); break; case ADTYPE_VIDEO: adm = ''; break; + case ADTYPE_NATIVE: + adm = JSON.stringify({ native: NATIVE_RESPONSE }) + break; default: throw Error('Invalid AdType'); } @@ -1030,7 +1273,7 @@ describe('smaatoBidAdapterTest', () => { }); describe('non ad pod', () => { - it('single image reponse', () => { + it('single image response', () => { const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_IMG), buildBidRequest()); expect(bids).to.deep.equal([ @@ -1056,7 +1299,7 @@ describe('smaatoBidAdapterTest', () => { ]); }); - it('single richmedia reponse', () => { + it('single richmedia response', () => { const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_RICHMEDIA), buildBidRequest()); expect(bids).to.deep.equal([ @@ -1108,6 +1351,34 @@ describe('smaatoBidAdapterTest', () => { ]); }); + it('single native response', () => { + const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_NATIVE), buildBidRequest()); + + expect(bids).to.deep.equal([ + { + requestId: '226416e6e6bf41', + cpm: 0.01, + width: 350, + height: 50, + native: { + ortb: NATIVE_RESPONSE + }, + ttl: 300, + creativeId: 'CR69381', + dealId: '12345', + netRevenue: true, + currency: 'USD', + mediaType: 'native', + meta: { + advertiserDomains: ['smaato.com'], + agencyId: 'CM6523', + networkName: 'smaato', + mediaType: 'native' + } + } + ]); + }); + it('ignores bid response with invalid ad type', () => { const serverResponse = buildOpenRtbBidResponse(ADTYPE_IMG); serverResponse.headers.get = (header) => { diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 727a7d7c1d6..97250ad0ebc 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import { spec } from 'modules/smartadserverBidAdapter.js'; @@ -158,12 +159,44 @@ describe('Smart bid adapter tests', function () { } }; + var sellerDefinedAudience = [ + { + 'name': 'hearst.com', + 'ext': { 'segtax': 1 }, + 'segment': [ + { 'id': '1001' }, + { 'id': '1002' } + ] + } + ]; + + var sellerDefinedContext = [ + { + 'name': 'cnn.com', + 'ext': { 'segtax': 2 }, + 'segment': [ + { 'id': '2002' } + ] + } + ]; + it('Verify build request', function () { config.setConfig({ 'currency': { 'adServerCurrency': 'EUR' + }, + ortb2: { + 'user': { + 'data': sellerDefinedAudience + }, + 'site': { + 'content': { + 'data': sellerDefinedContext + } + } } }); + const request = spec.buildRequests(DEFAULT_PARAMS); expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); expect(request[0]).to.have.property('method').and.to.equal('POST'); @@ -186,6 +219,8 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('buid').and.to.equal('7569'); expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); expect(requestContent).to.have.property('ckid').and.to.equal(42); + expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); + expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); }); it('Verify parse response with no ad', function () { @@ -200,6 +235,27 @@ describe('Smart bid adapter tests', function () { }).to.not.throw(); }); + it('Should not nest response if ad and adUrl empty', () => { + const BID_RESPONSE_EMPTY = { + body: { + ad: null, + adUrl: null, + cpm: 0.92, + isNoAd: false + } + }; + + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse(BID_RESPONSE_EMPTY, request[0]); + + expect(bids).to.have.lengthOf(0); + expect(() => { + spec.interpretResponse(BID_RESPONSE_EMPTY, { + data: 'invalid Json' + }); + }).to.not.throw(); + }); + it('Verify parse response', function () { const request = spec.buildRequests(DEFAULT_PARAMS); const bids = spec.interpretResponse(BID_RESPONSE, request[0]); @@ -337,8 +393,8 @@ describe('Smart bid adapter tests', function () { describe('gdpr tests', function () { afterEach(function () { + config.setConfig({ ortb2: undefined }); config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with GDPR', function () { @@ -387,10 +443,33 @@ describe('Smart bid adapter tests', function () { }); }); + describe('GPP', function () { + it('should be added to payload when gppConsent available in bidder request', function () { + const options = { + gppConsent: { + gppString: 'some-gpp-string', + applicableSections: [3, 5] + } + }; + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, options); + const payload = JSON.parse(request[0].data); + + expect(payload).to.have.property('gpp').and.to.equal(options.gppConsent.gppString); + expect(payload).to.have.property('gpp_sid').and.to.be.an('array'); + expect(payload.gpp_sid).to.have.lengthOf(2).and.to.deep.equal(options.gppConsent.applicableSections); + }); + + it('should be undefined on payload when gppConsent unavailable in bidder request', function () { + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, {}); + const payload = JSON.parse(request[0].data); + + expect(payload.gpp).to.be.undefined; + }); + }); + describe('ccpa/us privacy tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); it('Verify build request with us privacy', function () { @@ -419,7 +498,6 @@ describe('Smart bid adapter tests', function () { describe('Instream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const INSTREAM_DEFAULT_PARAMS = [{ @@ -468,6 +546,16 @@ describe('Smart bid adapter tests', function () { config.setConfig({ 'currency': { 'adServerCurrency': 'EUR' + }, + ortb2: { + 'user': { + 'data': sellerDefinedAudience + }, + 'site': { + 'content': { + 'data': sellerDefinedContext + } + } } }); const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS); @@ -486,6 +574,8 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('buid').and.to.equal('7569'); expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); expect(requestContent).to.have.property('ckid').and.to.equal(42); + expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); + expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); expect(requestContent).to.have.property('isVideo').and.to.equal(true); expect(requestContent).to.have.property('videoData'); expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); @@ -678,7 +768,6 @@ describe('Smart bid adapter tests', function () { describe('Outstream video tests', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); const OUTSTREAM_DEFAULT_PARAMS = [{ @@ -727,6 +816,16 @@ describe('Smart bid adapter tests', function () { config.setConfig({ 'currency': { 'adServerCurrency': 'EUR' + }, + ortb2: { + 'user': { + 'data': sellerDefinedAudience + }, + 'site': { + 'content': { + 'data': sellerDefinedContext + } + } } }); const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS); @@ -745,6 +844,8 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('buid').and.to.equal('7579'); expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); expect(requestContent).to.have.property('ckid').and.to.equal(43); + expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); + expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); expect(requestContent).to.have.property('isVideo').and.to.equal(false); expect(requestContent).to.have.property('videoData'); expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(7); @@ -975,6 +1076,17 @@ describe('Smart bid adapter tests', function () { }); describe('Floors module', function () { + const getFloor = (bid) => { + switch (bid.mediaType) { + case BANNER: + return { currency: 'USD', floor: 1.93 }; + case VIDEO: + return { currency: 'USD', floor: 2.72 }; + default: + return {}; + } + }; + it('should include floor from bid params', function() { const bidRequest = JSON.parse((spec.buildRequests(DEFAULT_PARAMS))[0].data); expect(bidRequest.bidfloor).to.deep.equal(DEFAULT_PARAMS[0].params.bidfloor); @@ -1014,12 +1126,91 @@ describe('Smart bid adapter tests', function () { const floor = spec.getBidFloor(bidRequest, null); expect(floor).to.deep.equal(0); }); + + it('should take floor from bidder params over ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73, bidfloor: 1.25 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.25); + }); + + it('should take floor from banner ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(1.93); + }); + + it('should take floor from video ad unit', function() { + const bidRequest = [{ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const request = spec.buildRequests(bidRequest); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('bidfloor').and.to.equal(2.72); + }); + + it('should take floor from multiple media type ad unit', function() { + const bidRequest = [{ + mediaTypes: { + banner: { + sizes: [[300, 600]] + }, + video: { + context: 'outstream', + playerSize: [[640, 480]] + } + }, + getFloor, + params: { siteId: 1234, pageId: 678, formatId: 73 } + }]; + + const requests = spec.buildRequests(bidRequest); + expect(requests).to.have.lengthOf(2); + + const requestContents = requests.map(r => JSON.parse(r.data)); + const videoRequest = requestContents.filter(r => r.videoData)[0]; + expect(videoRequest).to.not.equal(null).and.to.not.be.undefined; + expect(videoRequest).to.have.property('bidfloor').and.to.equal(2.72); + + const bannerRequest = requestContents.filter(r => !r.videoData)[0]; + expect(bannerRequest).to.not.equal(null).and.to.not.be.undefined; + expect(bannerRequest).to.have.property('bidfloor').and.to.equal(1.93); + }); }); describe('Verify bid requests with multiple mediaTypes', function () { afterEach(function () { config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); }); var DEFAULT_PARAMS_MULTIPLE_MEDIA_TYPES = [{ diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 58ce50efb8e..c3d0711632e 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -342,6 +342,48 @@ describe('The smartx adapter', function () { expect(request.data.imp[0].video.minduration).to.equal(3); expect(request.data.imp[0].video.maxduration).to.equal(15); }); + + it('should pass schain param', function () { + var request; + + bid.schain = { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.source).to.deep.equal({ + ext: { + schain: { + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } + } + }) + }); + + it('should pass sitekey param', function () { + var request; + + bid.params.sitekey = 'foo' + + request = spec.buildRequests([bid], bidRequestObj)[0]; + + expect(request.data.site.content.ext.sitekey).to.equal('foo'); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js new file mode 100644 index 00000000000..b41b0280235 --- /dev/null +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -0,0 +1,320 @@ +import {expect} from 'chai'; +import {spec, ENDPOINT_PROTOCOL, ENDPOINT_DOMAIN, ENDPOINT_PATH} from 'modules/smartytechBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'smartytech'; + +describe('SmartyTechDSPAdapter: inherited functions', function () { + let adapter; + beforeEach(() => { + adapter = newBidder(spec); + }); + it('exists and is a function', function () { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); + }); + it(`bidder code is ${BIDDER_CODE}`, function () { + expect(spec.code).to.be.equal(BIDDER_CODE); + }) +}); + +describe('SmartyTechDSPAdapter: isBidRequestValid', function () { + it('Invalid bid request. Should return false', function () { + const bidFixture = { + params: { + use_id: 13144375 + } + } + + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + it('Valid bid request. Should return true', function () { + const bidFixture = { + params: { + endpointId: 13144375 + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check video block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check playerSize', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check context', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid video bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + video: { + playerSize: [300, 250], + context: 'instream' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); + + it('Invalid bid request. Check banner block', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: {} + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Invalid bid request. Check banner sizes', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: '300x250' + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.false + }); + + it('Valid bid request. valid banner bid', function () { + const bidFixture = { + params: { + endpointId: 1 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + } + expect(spec.isBidRequestValid(bidFixture)).to.be.true + }); +}); + +function mockRandomSizeArray(len) { + return Array.apply(null, {length: len}).map(i => { + return [Math.floor(Math.random() * 800), Math.floor(Math.random() * 800)] + }); +} + +function mockBidRequestListData(mediaType, size) { + return Array.apply(null, {length: size}).map((i, index) => { + const id = Math.floor(Math.random() * 800) * (index + 1); + let mediaTypes; + + if (mediaType == 'video') { + mediaTypes = { + video: { + playerSize: mockRandomSizeArray(1), + context: 'instream' + }, + } + } else { + mediaTypes = { + banner: { + sizes: mockRandomSizeArray(index + 1) + } + } + } + + return { + adUnitCode: `adUnitCode-${id}`, + mediaTypes: mediaTypes, + bidId: `bidId-${id}`, + params: { + endpointId: id + } + } + }); +} + +function mockRefererData() { + return { + refererInfo: { + page: 'https://some-test.page' + } + } +} + +function mockResponseData(requestData) { + let data = {} + requestData.data.forEach((request, index) => { + const rndIndex = Math.floor(Math.random() * 800); + let width, height, mediaType; + if (request.video !== undefined) { + width = request.video.playerSize[0][0]; + height = request.video.playerSize[0][1]; + mediaType = 'video'; + } else { + width = request.banner.sizes[0][0]; + height = request.banner.sizes[0][1]; + mediaType = 'banner'; + } + + data[request.adUnitCode] = { + ad: `ad-${rndIndex}`, + width: width, + height: height, + creativeId: `creative-id-${index}`, + cpm: Math.floor(Math.random() * 100), + currency: `UAH-${rndIndex}`, + mediaType: mediaType + }; + }); + return { + body: data + } +}; + +describe('SmartyTechDSPAdapter: buildRequests', () => { + let mockBidRequest; + let mockReferer; + beforeEach(() => { + mockBidRequest = mockBidRequestListData('banner', 8); + mockReferer = mockRefererData(); + }); + it('has return data', () => { + const request = spec.buildRequests(mockBidRequest, mockReferer); + expect(request).not.null; + }); + it('correct request URL', () => { + const request = spec.buildRequests(mockBidRequest, mockReferer); + expect(request.url).to.be.equal(`${ENDPOINT_PROTOCOL}://${ENDPOINT_DOMAIN}${ENDPOINT_PATH}`) + }); + it('correct request method', () => { + const request = spec.buildRequests(mockBidRequest, mockReferer); + expect(request.method).to.be.equal(`POST`) + }); + it('correct request data', () => { + const data = spec.buildRequests(mockBidRequest, mockReferer).data; + data.forEach((request, index) => { + expect(request.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode); + expect(request.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner); + expect(request.bidId).to.be.equal(mockBidRequest[index].bidId); + expect(request.endpointId).to.be.equal(mockBidRequest[index].params.endpointId); + expect(request.referer).to.be.equal(mockReferer.refererInfo.page); + }) + }); +}); + +describe('SmartyTechDSPAdapter: interpretResponse', () => { + let mockBidRequest; + let mockReferer; + let request; + let mockResponse; + beforeEach(() => { + const brData = mockBidRequestListData('banner', 2); + mockReferer = mockRefererData(); + request = spec.buildRequests(brData, mockReferer); + mockBidRequest = { + data: brData + } + mockResponse = mockResponseData(request); + }); + + it('interpretResponse: empty data request', () => { + delete mockResponse['body'] + const data = spec.interpretResponse(mockResponse, mockBidRequest); + expect(data.length).to.be.equal(0); + }); + + it('interpretResponse: response data and convert data arrays has same length', () => { + const keys = Object.keys(mockResponse.body); + const data = spec.interpretResponse(mockResponse, mockBidRequest); + expect(data.length).to.be.equal(keys.length); + }); + + it('interpretResponse: convert to correct data', () => { + const keys = Object.keys(mockResponse.body); + const data = spec.interpretResponse(mockResponse, mockBidRequest); + + data.forEach((responseItem, index) => { + expect(responseItem.ad).to.be.equal(mockResponse.body[keys[index]].ad); + expect(responseItem.cpm).to.be.equal(mockResponse.body[keys[index]].cpm); + expect(responseItem.creativeId).to.be.equal(mockResponse.body[keys[index]].creativeId); + expect(responseItem.currency).to.be.equal(mockResponse.body[keys[index]].currency); + expect(responseItem.netRevenue).to.be.true; + expect(responseItem.ttl).to.be.equal(60); + expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); + expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); + expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + }); + }); +}); + +describe('SmartyTechDSPAdapter: interpretResponse video', () => { + let mockBidRequest; + let mockReferer; + let request; + let mockResponse; + beforeEach(() => { + const brData = mockBidRequestListData('video', 2); + mockReferer = mockRefererData(); + request = spec.buildRequests(brData, mockReferer); + mockBidRequest = { + data: brData + } + mockResponse = mockResponseData(request); + }); + + it('interpretResponse: convert to correct data', () => { + const keys = Object.keys(mockResponse.body); + const data = spec.interpretResponse(mockResponse, mockBidRequest); + + data.forEach((responseItem, index) => { + expect(responseItem.ad).to.be.equal(mockResponse.body[keys[index]].ad); + expect(responseItem.cpm).to.be.equal(mockResponse.body[keys[index]].cpm); + expect(responseItem.creativeId).to.be.equal(mockResponse.body[keys[index]].creativeId); + expect(responseItem.currency).to.be.equal(mockResponse.body[keys[index]].currency); + expect(responseItem.netRevenue).to.be.true; + expect(responseItem.ttl).to.be.equal(60); + expect(responseItem.requestId).to.be.equal(mockBidRequest.data[index].bidId); + expect(responseItem.width).to.be.equal(mockResponse.body[keys[index]].width); + expect(responseItem.height).to.be.equal(mockResponse.body[keys[index]].height); + expect(responseItem.mediaType).to.be.equal(mockResponse.body[keys[index]].mediaType); + expect(responseItem.vastXml).to.be.equal(mockResponse.body[keys[index]].ad); + }); + }); +}); diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index b9a816cf3d5..44d2c7c6507 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -1,9 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/smilewantedBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { requestBidsHook } from 'modules/consentManagement.js'; const DISPLAY_REQUEST = [{ adUnitCode: 'sw_300x250', @@ -20,6 +17,37 @@ const DISPLAY_REQUEST = [{ transactionId: 'trans_abcd1234' }]; +const DISPLAY_REQUEST_WITH_EIDS = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: 1 + }, + requestId: 'request_abcd1234', + transactionId: 'trans_abcd1234', + userIdAsEids: [{ + source: 'pubcid.org', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + }] + }] +}]; + const DISPLAY_REQUEST_WITH_POSITION_TYPE = [{ adUnitCode: 'sw_300x250', bidId: '12345', @@ -170,6 +198,29 @@ describe('smilewantedBidAdapterTests', function () { expect(requestContent).to.have.property('pageDomain').and.to.equal('https://localhost/Prebid.js/integrationExamples/gpt/hello_world.html'); }); + it('Verify external ids in request and ids found', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(DISPLAY_REQUEST_WITH_EIDS, {}); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('eids'); + expect(requestContent.eids).to.not.equal(null).and.to.not.be.undefined; + expect(requestContent.eids.length).to.greaterThan(0); + for (let index in requestContent.eids) { + let eid = requestContent.eids[index]; + expect(eid.source).to.not.equal(null).and.to.not.be.undefined; + expect(eid.uids).to.not.equal(null).and.to.not.be.undefined; + for (let uidsIndex in eid.uids) { + let uid = eid.uids[uidsIndex]; + expect(uid.id).to.not.equal(null).and.to.not.be.undefined; + } + } + }); + describe('gdpr tests', function () { afterEach(function () { config.resetConfig(); diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 7803cfff394..56ed4d5196e 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -287,6 +287,7 @@ describe('SonobiBidAdapter', function () { }, mediaTypes: { video: { + sizes: [[300, 250], [300, 600]], context: 'outstream' } } @@ -331,7 +332,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d||f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; @@ -370,7 +371,7 @@ describe('SonobiBidAdapter', function () { } } }; - const bidRequests = spec.buildRequests(bidRequest, {...bidderRequests, ortb2}); + const bidRequests = spec.buildRequests(bidRequest, { ...bidderRequests, ortb2 }); expect(bidRequests.data.fpd).to.equal(JSON.stringify(ortb2)); }); @@ -539,7 +540,7 @@ describe('SonobiBidAdapter', function () { ]); }); - it('should return a properly formatted request with userid as a JSON-encoded set of User ID results', function () { + it('should return a properly formatted request with the userid value omitted when the userId object is present on the bidRequest. ', function () { bidRequest[0].userId = { 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': { 'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': { 'linkType': 2 } } }; bidRequest[1].userId = { 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': { 'uid': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ', 'ext': { 'linkType': 2 } } }; const bidRequests = spec.buildRequests(bidRequest, bidderRequests); @@ -547,29 +548,7 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.method).to.equal('GET'); expect(bidRequests.data.ref).not.to.be.empty; expect(bidRequests.data.s).not.to.be.empty; - expect(JSON.parse(bidRequests.data.userid)).to.eql({ 'pubcid': 'abcd-efg-0101', 'tdid': 'td-abcd-efg-0101', 'id5id': 'ID5-ZHMOrVeUVTUKgrZ-a2YGxeh5eS_pLzHCQGYOEAiTBQ' }); - }); - - it('should return a properly formatted request with userid omitted if there are no userIds', function () { - bidRequest[0].userId = {}; - bidRequest[1].userId = {}; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.userid).to.equal(undefined); - }); - - it('should return a properly formatted request with userid omitted', function () { - bidRequest[0].userId = undefined; - bidRequest[1].userId = undefined; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.url).to.equal('https://apex.go.sonobi.com/trinity.json'); - expect(bidRequests.method).to.equal('GET'); - expect(bidRequests.data.ref).not.to.be.empty; - expect(bidRequests.data.s).not.to.be.empty; - expect(bidRequests.data.userid).to.equal(undefined); + expect(bidRequests.data.userid).to.be.undefined; }); it('should return a properly formatted request with keywrods included as a csv of strings', function () { diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js index 8284bb54e9b..68552eb3d8a 100644 --- a/test/spec/modules/sovrnAnalyticsAdapter_spec.js +++ b/test/spec/modules/sovrnAnalyticsAdapter_spec.js @@ -1,8 +1,10 @@ import sovrnAnalyticsAdapter from '../../../modules/sovrnAnalyticsAdapter.js'; -import { expect } from 'chai'; +import {expect} from 'chai'; import {config} from 'src/config.js'; import adaptermanager from 'src/adapterManager.js'; -import { server } from 'test/mocks/xhr.js'; +import {server} from 'test/mocks/xhr.js'; +import {expectEvents, fireEvents} from '../../helpers/analytics.js'; + var assert = require('assert'); let events = require('src/events'); @@ -195,15 +197,7 @@ describe('Sovrn Analytics Adapter', function () { sovrnId: 123 } }); - - events.emit(constants.EVENTS.AUCTION_INIT, {}); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); - - // 5 SovrnAnalytics events + 1 Clean.io event - sinon.assert.callCount(sovrnAnalyticsAdapter.track, 6); + expectEvents().to.beTrackedBy(sovrnAnalyticsAdapter.track); }); it('should catch no events if no affiliate id', function () { @@ -212,13 +206,7 @@ describe('Sovrn Analytics Adapter', function () { options: { } }); - - events.emit(constants.EVENTS.AUCTION_INIT, {}); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); - + fireEvents(); sinon.assert.callCount(sovrnAnalyticsAdapter.track, 0); }); }); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index b41a17a8e0a..0c01244033e 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -2,9 +2,10 @@ import {expect} from 'chai'; import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' import * as utils from '../../../src/utils' +import {server} from '../../mocks/xhr' describe('Taboola Adapter', function () { - let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie; + let hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie, commonBidRequest; beforeEach(() => { hasLocalStorage = sinon.stub(userData.storageManager, 'hasLocalStorage'); @@ -12,7 +13,7 @@ describe('Taboola Adapter', function () { getCookie = sinon.stub(userData.storageManager, 'getCookie'); getDataFromLocalStorage = sinon.stub(userData.storageManager, 'getDataFromLocalStorage'); localStorageIsEnabled = sinon.stub(userData.storageManager, 'localStorageIsEnabled'); - + commonBidRequest = createBidRequest(); $$PREBID_GLOBAL$$.bidderSettings = { taboola: { storageAllowed: true @@ -30,19 +31,19 @@ describe('Taboola Adapter', function () { $$PREBID_GLOBAL$$.bidderSettings = {}; }) - const commonBidRequest = { + const displayBidRequestParams = { + sizes: [[300, 250], [300, 600]] + } + + const createBidRequest = () => ({ bidder: 'taboola', params: { publisherId: 'publisherId', tagId: 'placement name' }, - bidId: 'aa43860a-4644-442a-b5e0-93f268cs4d19', - auctionId: '65746dca-26f3-4186-be13-dfa63469b1b7', - } - - const displayBidRequestParams = { - sizes: [[300, 250], [300, 600]] - } + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }); describe('isBidRequestValid', function () { it('should fail when bid is invalid - tagId isn`t defined', function () { @@ -89,50 +90,36 @@ describe('Taboola Adapter', function () { } expect(spec.isBidRequestValid(bid)).to.equal(true) }) + }) - it('should succeed when url is null', function () { - const bid = { - bidder: 'taboola', - params: { - publisherId: 'publisherId', - tagId: 'below the article', - endpointUrl: null - }, - ...displayBidRequestParams - } - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) - - it('should succeed when url is empty string', function () { - const bid = { - bidder: 'taboola', - params: { - publisherId: 'publisherId', - tagId: 'below the article', - endpointUrl: '' - }, - ...displayBidRequestParams - } - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) + describe('onBidWon', function () { + it('onBidWon exist as a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); - it('should succeed when url is filled', function () { + it('should resolve price macro in nurl', function () { + const nurl = 'http://win.example.com/${AUCTION_PRICE}'; const bid = { - bidder: 'taboola', - params: { - publisherId: 'publisherId', - tagId: 'below the article', - endpointUrl: 'https://example.com' - }, - ...displayBidRequestParams + requestId: 1, + cpm: 2, + originalCpm: 3.4, + creativeId: 1, + ttl: 60, + netRevenue: true, + mediaType: 'banner', + ad: '...', + width: 300, + height: 250, + nurl: nurl } - expect(spec.isBidRequestValid(bid)).to.equal(true) - }) - }) + spec.onBidWon(bid); + expect(server.requests[0].url).to.equals('http://win.example.com/3.4') + }); + }); describe('buildRequests', function () { const defaultBidRequest = { - ...commonBidRequest, + ...createBidRequest(), ...displayBidRequestParams, } @@ -161,7 +148,8 @@ describe('Taboola Adapter', function () { }, 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, - 'bidfloorcur': 'USD' + 'bidfloorcur': 'USD', + 'ext': {} }], 'site': { 'id': commonBidRequest.params.publisherId, @@ -176,11 +164,13 @@ describe('Taboola Adapter', function () { 'source': {'fd': 1}, 'bcat': [], 'badv': [], + 'wlang': [], 'user': { 'buyeruid': 0, 'ext': {}, }, - 'regs': {'coppa': 0, 'ext': {}} + 'regs': {'coppa': 0, 'ext': {}}, + 'ext': {} }; const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); @@ -189,44 +179,6 @@ describe('Taboola Adapter', function () { expect(res.data).to.deep.equal(JSON.stringify(expectedData)); }); - it('should fill the url when it is passed', function () { - const commonBidRequestWithUrl = { - bidder: 'taboola', - params: { - publisherId: 'publisherId', - tagId: 'placement name', - endpointUrl: 'https://example.com' - }, - bidId: 'aa43860a-4644-442a-b5e0-93f268cs4d19', - auctionId: '65746dca-26f3-4186-be13-dfa63469b1b7', - } - let defaultBidRequestWithUrl = { - ...commonBidRequestWithUrl, - ...displayBidRequestParams, - } - const res = spec.buildRequests([defaultBidRequestWithUrl], commonBidderRequest); - expect(res.url).to.equal(`${commonBidRequestWithUrl.params.endpointUrl}/${commonBidRequest.params.publisherId}`); - }) - - it('should fill default url when url param is empty string', function () { - const commonBidRequestWithUrl = { - bidder: 'taboola', - params: { - publisherId: 'publisherId', - tagId: 'placement name', - endpointUrl: '' - }, - bidId: 'aa43860a-4644-442a-b5e0-93f268cs4d19', - auctionId: '65746dca-26f3-4186-be13-dfa63469b1b7', - } - let defaultBidRequestWithUrl = { - ...commonBidRequestWithUrl, - ...displayBidRequestParams, - } - const res = spec.buildRequests([defaultBidRequestWithUrl], commonBidderRequest); - expect(res.url).to.equal(`${END_POINT_URL}/${commonBidRequestWithUrl.params.publisherId}`); - }) - it('should pass optional parameters in request', function () { const optionalParams = { bidfloor: 0.25, @@ -244,6 +196,77 @@ describe('Taboola Adapter', function () { expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); }); + it('should pass bid floor', function () { + const bidRequest = { + ...defaultBidRequest, + params: {...commonBidRequest.params}, + getFloor: function() { + return { + currency: 'USD', + floor: 2.7, + } + } + }; + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].bidfloor).to.deep.equal(2.7); + expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); + }); + + it('should pass bid floor even if it is a bid floor param', function () { + const optionalParams = { + bidfloor: 0.25, + bidfloorcur: 'EUR' + }; + + const bidRequest = { + ...defaultBidRequest, + params: {...commonBidRequest.params, ...optionalParams}, + getFloor: function() { + return { + currency: 'USD', + floor: 2.7, + } + } + }; + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].bidfloor).to.deep.equal(2.7); + expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); + }); + + it('should pass impression position', function () { + const optionalParams = { + position: 2 + }; + + const bidRequest = { + ...defaultBidRequest, + params: {...commonBidRequest.params, ...optionalParams} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].banner.pos).to.deep.equal(2); + }); + + it('should pass gpid if configured', function () { + const ortb2Imp = { + ext: { + gpid: '/homepage/#1' + } + } + const bidRequest = { + ...defaultBidRequest, + ortb2Imp: ortb2Imp, + params: {...commonBidRequest.params} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data); + expect(resData.imp[0].ext.gpid).to.deep.equal('/homepage/#1'); + }); + it('should pass bidder timeout', function () { const bidderRequest = { ...commonBidderRequest, @@ -254,6 +277,40 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500); }); + describe('first party data', function () { + it('should parse first party data', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + bcat: ['EX1', 'EX2', 'EX3'], + badv: ['site.com'], + wlang: ['de'], + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.bcat).to.deep.equal(bidderRequest.ortb2.bcat) + expect(resData.badv).to.deep.equal(bidderRequest.ortb2.badv) + expect(resData.wlang).to.deep.equal(bidderRequest.ortb2.wlang) + }); + + it('should pass pageType if exists in ortb2', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + ext: { + data: { + pageType: 'news' + } + } + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.ext.pageType).to.deep.equal(bidderRequest.ortb2.ext.data.pageType); + }); + }); + describe('handle privacy segments when building request', function () { it('should pass GDPR consent', function () { const bidderRequest = { @@ -272,6 +329,20 @@ describe('Taboola Adapter', function () { expect(resData.regs.ext.gdpr).to.equal(1) }); + it('should pass GPP consent if exist in ortb2', function () { + const ortb2 = { + regs: { + gpp: 'testGpp', + gpp_sid: [1, 2, 3] + } + } + + const res = spec.buildRequests([defaultBidRequest], {...commonBidderRequest, ortb2}) + const resData = JSON.parse(res.data) + expect(resData.regs.ext.gpp).to.equal('testGpp') + expect(resData.regs.ext.gpp_sid).to.deep.equal([1, 2, 3]) + }); + it('should pass us privacy consent', function () { const bidderRequest = { refererInfo: { @@ -389,7 +460,9 @@ describe('Taboola Adapter', function () { 'w': 300, 'h': 250, 'exp': 60, - 'lurl': 'http://us-trc.taboola.com/sample' + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/', + } ], 'seat': '14204545260' @@ -441,6 +514,110 @@ describe('Taboola Adapter', function () { overriddenServerResponse.body.seatbid[0].bid = bid; }); + it('should interpret multi impression request', function () { + const multiRequest = { + bids: [ + { + ...createBidRequest(), + ...displayBidRequestParams + }, + { + ...createBidRequest(), + ...displayBidRequestParams + } + ] + } + + const multiServerResponse = { + body: { + 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '2', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': 'ADM2', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + }, + { + 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', + 'impid': '1', + 'price': 0.342068, + 'adid': '2785119545551083381', + 'adm': 'ADM1', + 'adomain': [ + 'example.xyz' + ], + 'cid': '15744349', + 'crid': '278195503434041083381', + 'w': 300, + 'h': 250, + 'exp': 60, + 'lurl': 'http://us-trc.taboola.com/sample', + 'nurl': 'http://win.example.com/' + + } + ], + 'seat': '14204545260' + } + ], + 'bidid': 'da43860a-4644-442a-b5e0-93f268cf8d19', + 'cur': 'USD' + } + }; + + const [bid] = multiServerResponse.body.seatbid[0].bid; + const expectedRes = [ + { + requestId: multiRequest.bids[1].bidId, + cpm: bid.price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponse.body.cur, + mediaType: 'banner', + ad: multiServerResponse.body.seatbid[0].bid[0].adm, + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + }, + { + requestId: multiRequest.bids[0].bidId, + cpm: bid.price, + creativeId: bid.crid, + ttl: 60, + netRevenue: true, + currency: multiServerResponse.body.cur, + mediaType: 'banner', + ad: multiServerResponse.body.seatbid[0].bid[1].adm, + width: bid.w, + height: bid.h, + nurl: 'http://win.example.com/', + meta: { + 'advertiserDomains': bid.adomain + }, + } + ] + + const res = spec.interpretResponse(multiServerResponse, multiRequest) + expect(res).to.deep.equal(expectedRes) + }); + it('should interpret display response', function () { const [bid] = serverResponse.body.seatbid[0].bid; const expectedRes = [ @@ -455,6 +632,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -481,6 +659,7 @@ describe('Taboola Adapter', function () { ad: bid.adm, width: bid.w, height: bid.h, + nurl: 'http://win.example.com/', meta: { 'advertiserDomains': bid.adomain }, @@ -491,8 +670,36 @@ describe('Taboola Adapter', function () { }); }) - describe('userData', function () { - // todo: add UT for getUserSyncs + describe('getUserSyncs', function () { + const usersyncUrl = 'https://trc.taboola.com/sg/prebidJS/1/cm'; + + it('should not return user sync if pixelEnabled is false', function () { + const res = spec.getUserSyncs({pixelEnabled: false}); + expect(res).to.be.an('array').that.is.empty; + }); + + it('should return user sync if pixelEnabled is true', function () { + const res = spec.getUserSyncs({pixelEnabled: true}); + expect(res).to.deep.equal([{type: 'image', url: usersyncUrl}]); + }); + + it('should pass consent tokens values', function() { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'GDPR_CONSENT'}, 'USP_CONSENT')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT', 'GPP_STRING')).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT&gpp=GPP_STRING` + }]); + }); }) describe('internal functions', function () { diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index adbc982e509..8180183e6d7 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -58,7 +58,7 @@ describe('TargetVideo Bid Adapter', function() { 'uuid': '84ab500420319d', 'ads': [{ 'ad_type': 'video', - 'cpm': 0.500000, + 'cpm': 0.675000, 'notify_url': 'https://www.target-video.com/', 'rtb': { 'video': { @@ -87,7 +87,7 @@ describe('TargetVideo Bid Adapter', function() { const bid = bidResponse[0]; expect(bid).to.not.be.empty; - expect(bid.cpm).to.equal(0.675); + expect(bid.cpm).to.equal(0.5); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.ad).to.include('') diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 254f23d011e..e981d80c8ae 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -418,6 +418,74 @@ describe('teadsBidAdapter', () => { }); }); + it('should add userAgentClientHints info to payload if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + ortb2: { + device: { + sua: { + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + } + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + } + } + }); + + const requestWithUserAgentClientHints = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(requestWithUserAgentClientHints.data); + + expect(payload.userAgentClientHints).to.exist; + expect(payload.userAgentClientHints).to.deep.equal({ + source: 2, + platform: { + brand: 'macOS', + version: [ '12', '4', '0' ] + }, + browsers: [ + { + brand: 'Chromium', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Google Chrome', + version: [ '106', '0', '5249', '119' ] + }, + { + brand: 'Not;A=Brand', + version: [ '99', '0', '0', '0' ] + } + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + ); + + const defaultRequest = spec.buildRequests(bidRequests, bidderResquestDefault); + expect(JSON.parse(defaultRequest.data).userAgentClientHints).to.not.exist; + }); + it('should use good mediaTypes video sizes', function() { const mediaTypesVideoSizes = { 'mediaTypes': { @@ -555,34 +623,79 @@ describe('teadsBidAdapter', () => { }) describe('First-party cookie Teads ID', function () { - it('should not add firstPartyCookieTeadsId param to payload if cookies are not enabled', function () { + it('should not add firstPartyCookieTeadsId param to payload if cookies are not enabled' + + ' and teads user id not available', function () { cookiesAreEnabledStub.returns(false); - const request = spec.buildRequests([baseBidRequest], bidderResquestDefault); + const bidRequest = { + ...baseBidRequest, + userId: { + pubcid: 'publisherFirstPartyViewerId-id' + } + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); const payload = JSON.parse(request.data); expect(payload).not.to.have.property('firstPartyCookieTeadsId'); }); - it('should not add firstPartyCookieTeadsId param to payload if first-party cookie is not available', function () { + it('should not add firstPartyCookieTeadsId param to payload if cookies are enabled ' + + 'but first-party cookie and teads user id are not available', function () { cookiesAreEnabledStub.returns(true); getCookieStub.withArgs('_tfpvi').returns(undefined); - const request = spec.buildRequests([baseBidRequest], bidderResquestDefault); + const bidRequest = { + ...baseBidRequest, + userId: { + pubcid: 'publisherFirstPartyViewerId-id' + } + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); const payload = JSON.parse(request.data); expect(payload).not.to.have.property('firstPartyCookieTeadsId'); }); - it('should add firstPartyCookieTeadsId param to payload if first-party cookie is available', function () { + it('should add firstPartyCookieTeadsId from cookie if it\'s available ' + + 'and teads user id is not', function () { cookiesAreEnabledStub.returns(true); getCookieStub.withArgs('_tfpvi').returns('my-teads-id'); - const request = spec.buildRequests([baseBidRequest], bidderResquestDefault); + const bidRequest = { + ...baseBidRequest, + userId: { + pubcid: 'publisherFirstPartyViewerId-id' + } + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); expect(payload.firstPartyCookieTeadsId).to.equal('my-teads-id'); }); + + it('should add firstPartyCookieTeadsId from user id module if it\'s available ' + + 'even if cookie is available too', function () { + cookiesAreEnabledStub.returns(true); + getCookieStub.withArgs('_tfpvi').returns('my-teads-id'); + + const bidRequest = { + ...baseBidRequest, + userId: { + pubcid: 'publisherFirstPartyViewerId-id', + teadsId: 'teadsId-fake-id' + } + }; + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + + const payload = JSON.parse(request.data); + + expect(payload.firstPartyCookieTeadsId).to.equal('teadsId-fake-id'); + }); }); }); diff --git a/test/spec/modules/teadsIdSystem_spec.js b/test/spec/modules/teadsIdSystem_spec.js new file mode 100644 index 00000000000..7b977e2fb2b --- /dev/null +++ b/test/spec/modules/teadsIdSystem_spec.js @@ -0,0 +1,271 @@ +import { + teadsIdSubmodule, + storage, + buildAnalyticsTagUrl, + getGdprStatus, + gdprStatus, + getPublisherId, + getGdprConsentString, + getCookieExpirationDate, getTimestampFromDays, getCcpaConsentString +} from 'modules/teadsIdSystem.js'; +import {server} from 'test/mocks/xhr.js'; +import * as utils from '../../../src/utils.js'; + +const FP_TEADS_ID_COOKIE_NAME = '_tfpvi'; +const teadsCookieIdSent = 'teadsCookieIdSent'; +const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; + +describe('TeadsIdSystem', function () { + describe('buildAnalyticsTagUrl', function () { + it('return complete URL of server request', function () { + const submoduleConfig = { + params: { + pubId: 1234 + } + }; + + const consentData = { + gdprApplies: true, + consentString: 'abc123==' + } + + const result = buildAnalyticsTagUrl(submoduleConfig, consentData); + const expected = 'https://at.teads.tv/fpc?analytics_tag_id=PUB_1234&tfpvi=&gdpr_consent=abc123%3D%3D&gdpr_status=12&gdpr_reason=120&ccpa_consent=&sv=prebid-v1' + expect(result).to.be.equal(expected); + }) + }); + + describe('getPublisherId', function () { + it('return empty string if params.pubId param is missing', function () { + const submoduleConfig = {}; + const result = getPublisherId(submoduleConfig); + const expected = ''; + expect(result).to.be.equal(expected); + }); + it('return empty string if params.pubId param has a wrong value', function () { + const pubIdDifferentValues = [null, undefined, false, true, {}, [], 'abc123']; + + pubIdDifferentValues.forEach((value) => { + const submoduleConfig = { + params: { + pubId: value + } + }; + const result = getPublisherId(submoduleConfig); + const expected = ''; + expect(result).to.be.equal(expected); + }); + }); + it('return a publisherId prefixed by PUB_ string if params.pubId has a good value', function () { + const pubIdDifferentValues = [1234, '1234']; + + pubIdDifferentValues.forEach((value) => { + const submoduleConfig = { + params: { + pubId: value + } + }; + const result = getPublisherId(submoduleConfig); + const expected = 'PUB_1234'; + expect(result).to.be.equal(expected); + }); + }); + }); + + describe('getGdprConsentString', function () { + it('return empty consentString if no consentData given', function () { + const consentDataDifferentValues = [{}, undefined, null]; + + consentDataDifferentValues.forEach((value) => { + const result = getGdprConsentString(value); + const expected = ''; + expect(result).to.be.equal(expected); + }); + }); + it('return empty consentString if consentString has a wrong format', function () { + const consentStringDifferentValues = [1, 0, undefined, null, {}]; + + consentStringDifferentValues.forEach((value) => { + const consentData = { + consentString: value + }; + const result = getGdprConsentString(consentData); + const expected = ''; + expect(result).to.be.equal(expected); + }); + }); + it('return a good consentString if present and a correct format', function () { + const consentData = { + consentString: 'consent-string-teads-O1' + }; + const result = getGdprConsentString(consentData); + const expected = 'consent-string-teads-O1'; + expect(result).to.be.equal(expected); + }); + }); + + describe('getCcpaConsentString', function () { + it('return empty CCPA consentString if no CCPA data is available or a wrong format', function () { + const consentDataDifferentValues = [{}, undefined, null, 123]; + + consentDataDifferentValues.forEach((value) => { + const result = getCcpaConsentString(value); + const expected = ''; + expect(result).to.be.equal(expected); + }); + }); + it('return CCPA consentString if CCPA data is available and a good format', function () { + const consentData = '1NYN'; + const result = getCcpaConsentString(consentData); + const expected = '1NYN'; + expect(result).to.be.equal(expected); + }); + }); + + describe('getGdprStatus', function () { + it('return CMP_NOT_FOUND_OR_ERROR if no given consentData', function () { + const consentDataDifferentValues = [{}, undefined, null]; + + consentDataDifferentValues.forEach((value) => { + const result = getGdprStatus(value); + const expected = gdprStatus.CMP_NOT_FOUND_OR_ERROR; + expect(result).to.be.equal(expected); + }); + }); + it('return CMP_NOT_FOUND_OR_ERROR if gdprApplies param has a wrong format', function () { + const gdprAppliesDifferentValues = ['yes', 'true', 1, 0, undefined, null, {}]; + + gdprAppliesDifferentValues.forEach((value) => { + const consentData = { + gdprApplies: value + }; + const result = getGdprStatus(consentData); + const expected = gdprStatus.CMP_NOT_FOUND_OR_ERROR; + expect(result).to.be.equal(expected); + }); + }); + it('return GDPR_DOESNT_APPLY if gdprApplies are false', function () { + const consentData = { + gdprApplies: false + }; + const result = getGdprStatus(consentData); + const expected = gdprStatus.GDPR_DOESNT_APPLY; + expect(result).to.be.equal(expected); + }); + it('return GDPR_APPLIES_PUBLISHER if gdprApplies are true', function () { + const consentData = { + gdprApplies: true + }; + const result = getGdprStatus(consentData); + const expected = gdprStatus.GDPR_APPLIES_PUBLISHER; + expect(result).to.be.equal(expected); + }) + }); + + describe('getExpirationDate', function () { + it('return Date Formatted if no given consentData', function () { + let timeStampStub; + timeStampStub = sinon.stub(utils, 'timestamp').returns(Date.UTC(2022, 8, 19, 14, 30, 50, 0)); + const cookiesMaxAge = getTimestampFromDays(365); + + const result = getCookieExpirationDate(cookiesMaxAge); + const expected = 'Tue, 19 Sep 2023 14:30:50 GMT'; + expect(result).to.be.equal(expected); + timeStampStub.restore(); + }); + }); + + describe('getId', function () { + let setCookieStub, setLocalStorageStub, removeFromLocalStorageStub, logErrorStub, logInfoStub, timeStampStub; + const teadsUrl = 'https://at.teads.tv/fpc'; + const timestampNow = new Date().getTime(); + + beforeEach(function () { + setCookieStub = sinon.stub(storage, 'setCookie'); + timeStampStub = sinon.stub(utils, 'timestamp').returns(timestampNow); + logErrorStub = sinon.stub(utils, 'logError'); + logInfoStub = sinon.stub(utils, 'logInfo'); + }); + + afterEach(function () { + setCookieStub.restore(); + timeStampStub.restore(); + logErrorStub.restore(); + logInfoStub.restore(); + }); + + it('should log an error and continue to callback if request errors', function () { + const config = { + params: {} + }; + + const callbackSpy = sinon.spy(); + const callback = teadsIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.include(teadsUrl); + request.respond(503, null, 'Unavailable'); + expect(logErrorStub.calledOnce).to.be.true; + }); + + it('should log an info and continue to callback if status 204 is sent', function () { + const config = { + params: {} + }; + + const callbackSpy = sinon.spy(); + const callback = teadsIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.include(teadsUrl); + request.respond(204, null, 'Unavailable'); + expect(logInfoStub.calledOnce).to.be.true; + }); + + it('should call the callback with the response body if status 200 is sent', function () { + const config = { + params: {} + }; + + const callbackSpy = sinon.spy(); + const callback = teadsIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.include(teadsUrl); + request.respond(200, {'Content-Type': 'application/json'}, teadsCookieIdSent); + expect(callbackSpy.lastCall.lastArg).to.deep.equal(teadsCookieIdSent); + }); + + it('should save teadsId in cookie and local storage if it was returned by API', function () { + const config = { + params: {} + }; + const result = teadsIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.deep.equal(teadsCookieIdSent); + }); + + let request = server.requests[0]; + request.respond(200, {'Content-Type': 'application/json'}, teadsCookieIdSent); + + const cookiesMaxAge = getTimestampFromDays(365); // 1 year + const expirationCookieDate = getCookieExpirationDate(cookiesMaxAge); + expect(setCookieStub.calledWith(FP_TEADS_ID_COOKIE_NAME, teadsCookieIdSent, expirationCookieDate)).to.be.true; + }); + + it('should delete teadsId in cookie and local storage if it was not returned by API', function () { + const config = { + params: {} + }; + const result = teadsIdSubmodule.getId(config, {}); + result.callback((id) => { + expect(id).to.be.undefined + }); + + let request = server.requests[0]; + request.respond(200, {'Content-Type': 'application/json'}, ''); + + expect(setCookieStub.calledWith(FP_TEADS_ID_COOKIE_NAME, '', EXPIRED_COOKIE_DATE)).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/topicsFpdModule_spec.js b/test/spec/modules/topicsFpdModule_spec.js index 3781768497b..958489f2728 100644 --- a/test/spec/modules/topicsFpdModule_spec.js +++ b/test/spec/modules/topicsFpdModule_spec.js @@ -1,5 +1,15 @@ -import {getTopics, getTopicsData, processFpd} from '../../../modules/topicsFpdModule.js'; -import {deepClone} from '../../../src/utils.js'; +import { + getTopics, + getTopicsData, + processFpd, + hasGDPRConsent, + getCachedTopics, + receiveMessage, + topicStorageName +} from '../../../modules/topicsFpdModule.js'; +import {deepClone, safeJSONParse} from '../../../src/utils.js'; +import {gdprDataHandler} from 'src/adapterManager.js'; +import {getCoreStorageManager} from 'src/storageManager.js'; describe('getTopicsData', () => { function makeTopic(topic, modelv, taxv = '1') { @@ -7,14 +17,14 @@ describe('getTopicsData', () => { topic, taxonomyVersion: taxv, modelVersion: modelv - } + }; } function byTaxClass(segments) { return segments.reduce((memo, segment) => { memo[`${segment.ext.segtax}:${segment.ext.segclass}`] = segment; return memo; - }, {}) + }, {}); } [ @@ -144,16 +154,16 @@ describe('getTopicsData', () => { Object.entries(byTaxClass(actual)).forEach(([key, datum]) => { sinon.assert.match(datum, expected[key]); expect(datum.name).to.equal('mockName'); - }) + }); }); it('should not set name if null', () => { getTopicsData(null, topics).forEach((data) => { expect(data.hasOwnProperty('name')).to.be.false; - }) - }) - }) - }) + }); + }); + }); + }); }); describe('getTopics', () => { @@ -167,11 +177,13 @@ describe('getTopics', () => { 'document that throws on featurePolicy': { browsingTopics: sinon.stub(), get featurePolicy() { - throw new Error() + throw new Error(); } }, 'document that throws on browsingTopics': { - browsingTopics: sinon.stub().callsFake(() => { throw new Error(); }), + browsingTopics: sinon.stub().callsFake(() => { + throw new Error(); + }), featurePolicy: { allowsFeature: sinon.stub().returns(true) } @@ -181,11 +193,11 @@ describe('getTopics', () => { return getTopics(doc).then((topics) => { expect(topics).to.eql([]); }); - }) + }); }); it('should call `document.browsingTopics` when allowed', () => { - const topics = ['t1', 't2'] + const topics = ['t1', 't2']; return getTopics({ browsingTopics: sinon.stub().returns(Promise.resolve(topics)), featurePolicy: { @@ -193,9 +205,9 @@ describe('getTopics', () => { } }).then((actual) => { expect(actual).to.eql(topics); - }) - }) -}) + }); + }); +}); describe('processFpd', () => { const mockData = [ @@ -237,3 +249,184 @@ describe('processFpd', () => { }); }); }); + +describe('Topics Module GDPR consent check', () => { + let gdprDataHdlrStub; + beforeEach(() => { + gdprDataHdlrStub = sinon.stub(gdprDataHandler, 'getConsentData'); + }); + + afterEach(() => { + gdprDataHdlrStub.restore(); + }); + + it('should return false when GDPR is applied but consent string is not present', () => { + const consentString = ''; + const consentConfig = { + consentString: consentString, + gdprApplies: true, + vendorData: {} + }; + gdprDataHdlrStub.returns(consentConfig); + expect(hasGDPRConsent()).to.equal(false); + }); + + it('should return true when GDPR doesn\'t apply', () => { + const consentString = 'CPi8wgAPi8wgAADABBENCrCsAP_AAH_AAAAAISNB7D=='; + const consentConfig = { + consentString: consentString, + gdprApplies: false, + vendorData: {} + }; + + gdprDataHdlrStub.returns(consentConfig); + expect(hasGDPRConsent()).to.equal(true); + }); + + it('should return true when GDPR is applied and purpose consent is true for all purpose[1,2,3,4]', () => { + const consentString = 'CPi8wgAPi8wgAADABBENCrCsAP_AAH_AAAAAISNB7D=='; + const consentConfig = { + consentString: consentString, + gdprApplies: true, + vendorData: { + metadata: consentString, + gdprApplies: true, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true + } + } + } + }; + + gdprDataHdlrStub.returns(consentConfig); + expect(hasGDPRConsent()).to.equal(true); + }); + + it('should return false when GDPR is applied and purpose consent is false for one of the purpose[1,2,3,4]', () => { + const consentString = 'CPi8wgAPi8wgAADABBENCrCsAP_AAH_AAAAAISNB7D=='; + const consentConfig = { + consentString: consentString, + gdprApplies: true, + vendorData: { + metadata: consentString, + gdprApplies: true, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: false + } + } + } + }; + + gdprDataHdlrStub.returns(consentConfig); + expect(hasGDPRConsent()).to.equal(false); + }); +}); + +describe('getCachedTopics()', () => { + const storage = getCoreStorageManager('topicsFpd'); + const expected = [{ + ext: { + segtax: 600, + segclass: '2206021246' + }, + segment: [{ + 'id': '243' + }, { + 'id': '265' + }], + name: 'ads.pubmatic.com' + }]; + const consentString = 'CPi8wgAPi8wgAADABBENCrCsAP_AAH_AAAAAISNB7D=='; + const consentConfig = { + consentString: consentString, + gdprApplies: true, + vendorData: { + metadata: consentString, + gdprApplies: true, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true + } + } + } + }; + const mockData = [ + { + name: 'domain', + segment: [{id: 123}] + }, + { + name: 'domain', + segment: [{id: 321}], + } + ]; + + const evt = { + data: '{"segment":{"domain":"ads.pubmatic.com","topics":[{"configVersion":"chrome.1","modelVersion":"2206021246","taxonomyVersion":"1","topic":165,"version":"chrome.1:1:2206021246"}],"bidder":"pubmatic"},"date":1669743901858}', + origin: 'https://ads.pubmatic.com' + }; + + let gdprDataHdlrStub; + beforeEach(() => { + gdprDataHdlrStub = sinon.stub(gdprDataHandler, 'getConsentData'); + }); + + afterEach(() => { + storage.removeDataFromLocalStorage(topicStorageName); + gdprDataHdlrStub.restore(); + }); + + it('should return segments for bidder if GDPR consent is true and there is cached segments stored which is not expired', () => { + const storedSegments = JSON.stringify( + [['pubmatic', { + '2206021246': { + 'ext': {'segtax': 600, 'segclass': '2206021246'}, + 'segment': [{'id': '243'}, {'id': '265'}], + 'name': 'ads.pubmatic.com' + }, + 'lastUpdated': new Date().getTime() + }]] + ); + storage.setDataInLocalStorage(topicStorageName, storedSegments); + gdprDataHdlrStub.returns(consentConfig); + assert.deepEqual(getCachedTopics(), expected); + }); + + it('should return empty segments for bidder if GDPR consent is true and there is cached segments stored which is expired', () => { + let storedSegments = '[["pubmatic",{"2206021246":{"ext":{"segtax":600,"segclass":"2206021246"},"segment":[{"id":"243"},{"id":"265"}],"name":"ads.pubmatic.com"},"lastUpdated":10}]]'; + storage.setDataInLocalStorage(topicStorageName, storedSegments); + gdprDataHdlrStub.returns(consentConfig); + assert.deepEqual(getCachedTopics(), []); + }); + + it('should stored segments if receiveMessage event is triggerred with segment data', () => { + return processFpd({}, {global: {}}, {data: Promise.resolve(mockData)}) + .then(({global}) => { + receiveMessage(evt); + let segments = new Map(safeJSONParse(storage.getDataFromLocalStorage(topicStorageName))); + expect(segments.has('pubmatic')).to.equal(true); + }); + }); + + it('should update stored segments if receiveMessage event is triggerred with segment data', () => { + let storedSegments = '[["pubmatic",{"2206021246":{"ext":{"segtax":600,"segclass":"2206021246"},"segment":[{"id":"243"},{"id":"265"}],"name":"ads.pubmatic.com"},"lastUpdated":1669719242027}]]'; + storage.setDataInLocalStorage(topicStorageName, storedSegments); + return processFpd({}, {global: {}}, {data: Promise.resolve(mockData)}) + .then(({global}) => { + receiveMessage(evt); + let segments = new Map(safeJSONParse(storage.getDataFromLocalStorage(topicStorageName))); + expect(segments.get('pubmatic')[2206021246].segment.length).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index e485ae3998f..221ffb8371f 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -725,11 +725,11 @@ describe('triplelift adapter', function () { it('should add amxRtbId to the payload if included', function () { const id = 'Ok9JQkBM-UFlAXEZQ-UUNBQlZOQzgrUFhW-UUNBQkRQTUBPQVpVWVxNXlZUUF9AUFhAUF9PXFY/'; - bidRequests[0].userIdAsEids = [{ source: 'amxrtb.com', uids: [{ id }] }]; + bidRequests[0].userIdAsEids = [{ source: 'amxdt.net', uids: [{ id }] }]; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload).to.exist; - expect(payload.user).to.deep.equal({ext: {eids: [{source: 'amxrtb.com', uids: [{id, ext: {rtiPartner: 'amxrtb.com'}}]}]}}); + expect(payload.user).to.deep.equal({ext: {eids: [{source: 'amxdt.net', uids: [{id, ext: {rtiPartner: 'amxdt.net'}}]}]}}); }); it('should add tdid, idl_env and criteoId to the payload if both are included', function () { @@ -979,6 +979,13 @@ describe('triplelift adapter', function () { expect(url).to.match(new RegExp('(?:' + prebid.version + ')')) expect(url).to.match(/(?:referrer)/); }); + it('should use refererInfo.page for referrer', function () { + bidderRequest.refererInfo.page = 'https://topmostlocation.com?foo=bar' + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const url = request.url; + expect(url).to.match(/(\?|&)referrer=https%3A%2F%2Ftopmostlocation.com%3Ffoo%3Dbar/); + delete bidderRequest.refererInfo.page + }); it('should return us_privacy param when CCPA info is available', function() { bidderRequest.uspConsent = '1YYY'; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); @@ -1161,6 +1168,19 @@ describe('triplelift adapter', function () { } }) }); + it('should add gpp consent data to bid request object if gpp data exists', function() { + bidderRequest.ortb2 = { + regs: { + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + } + } + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs).to.deep.equal({ + 'gpp': 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + 'gpp_sid': [7] + }) + }); }); describe('interpretResponse', function () { @@ -1417,6 +1437,13 @@ describe('triplelift adapter', function () { expect(result[0].meta.advertiserDomains[1]).to.equal('internetalerts.org'); expect(result[1].meta).to.not.have.key('advertiserDomains'); }); + + it('should include networkId in the meta field if available', function () { + let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); + expect(result[1].meta.networkId).to.equal('10092'); + expect(result[2].meta.networkId).to.equal('5989'); + expect(result[3].meta.networkId).to.equal('5989'); + }); }); describe('getUserSyncs', function() { diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 60c00f4e4b9..9869d072657 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -2,6 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/ttdBidAdapter'; import { deepClone } from 'src/utils.js'; import { config } from 'src/config'; +import { detectReferer } from 'src/refererDetection.js'; + +import { buildWindowTree } from '../../helpers/refererDetectionHelper'; describe('ttdBidAdapter', function () { function testBuildRequests(bidRequests, bidderRequestBase) { @@ -200,20 +203,15 @@ describe('ttdBidAdapter', function () { 'bidRequestsCount': 1 }]; + const testWindow = buildWindowTree(['https://www.example.com/test', 'https://www.example.com/other/page', 'https://www.example.com/third/page'], 'https://othersite.com/', 'https://example.com/canonical/page'); + const baseBidderRequestReferer = detectReferer(testWindow)(); const baseBidderRequest = { 'bidderCode': 'ttd', 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', 'bidderRequestId': '18084284054531', 'auctionStart': 1540945362095, 'timeout': 3000, - 'refererInfo': { - 'page': 'https://www.example.com/test', - 'reachedTop': true, - 'numIframes': 0, - 'stack': [ - 'https://www.example.com/test' - ] - }, + 'refererInfo': baseBidderRequestReferer, 'start': 1540945362099, 'doneCbCallCount': 0 }; @@ -291,6 +289,11 @@ describe('ttdBidAdapter', function () { expect(requestBody.site.page).to.equal('https://www.example.com/test'); }); + it('ensure top most location is used', function () { + const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; + expect(requestBody.site.page).to.equal('https://www.example.com/test'); + }); + it('sets the banner pos correctly if sent', function () { let clonedBannerRequests = deepClone(baseBannerBidRequests); clonedBannerRequests[0].mediaTypes.banner.pos = 1; @@ -310,6 +313,43 @@ describe('ttdBidAdapter', function () { expect(requestBody.imp[0].banner.expdir).to.equal(expdir); }); + it('merges first party site data', function () { + const ortb2 = { + site: { + publisher: { + domain: 'https://foo.bar', + } + } + }; + const baseBidderRequestWithoutRefererDomain = { + ...baseBidderRequest, + refererInfo: { + ...baseBannerBidRequests.referer, + domain: null + } + } + const requestBody = testBuildRequests( + baseBannerBidRequests, {...baseBidderRequestWithoutRefererDomain, ortb2} + ).data; + config.resetConfig(); + expect(requestBody.site.publisher).to.deep.equal({domain: 'https://foo.bar', id: '13144370'}); + }); + + it('referer domain overrides first party site data publisher domain', function () { + const ortb2 = { + site: { + publisher: { + domain: 'https://foo.bar', + } + } + }; + const requestBody = testBuildRequests( + baseBannerBidRequests, {...baseBidderRequest, ortb2} + ).data; + config.resetConfig(); + expect(requestBody.site.publisher.domain).to.equal(baseBidderRequest.refererInfo.domain); + }); + it('sets keywords properly if sent', function () { const ortb2 = { site: { @@ -387,6 +427,20 @@ describe('ttdBidAdapter', function () { expect(requestBody.regs.coppa).to.equal(1); }); + it('adds gpp consent info to the request', function () { + const ortb2 = { + regs: { + gpp: 'somegppstring', + gpp_sid: [6, 7] + } + }; + let clonedBidderRequest = {...deepClone(baseBidderRequest), ortb2}; + const requestBody = testBuildRequests(baseBannerBidRequests, clonedBidderRequest).data; + config.resetConfig(); + expect(requestBody.regs.gpp).to.equal('somegppstring'); + expect(requestBody.regs.gpp_sid).to.eql([6, 7]); + }); + it('adds schain info to the request', function () { const schain = { 'ver': '1.0', diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js new file mode 100644 index 00000000000..b33e8cda501 --- /dev/null +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -0,0 +1,310 @@ +import {coreStorage, init, setSubmoduleRegistry, requestBidsHook} from 'modules/userId/index.js'; +import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import { uid2IdSubmodule } from 'modules/uid2IdSystem.js'; +import 'src/prebid.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import { server } from 'test/mocks/xhr.js'; +import { configureTimerInterceptors } from 'test/mocks/timers.js'; +import {hook} from 'src/hook.js'; +import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; + +let expect = require('chai').expect; + +const clearTimersAfterEachTest = true; +const debugOutput = () => {}; + +const expireCookieDate = 'Thu, 01 Jan 1970 00:00:01 GMT'; +const msIn12Hours = 60 * 60 * 12 * 1000; +const moduleCookieName = '__uid2_advertising_token'; +const publisherCookieName = '__UID2_SERVER_COOKIE'; +const auctionDelayMs = 10; +const legacyConfigParams = null; +const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName } +const getFutureCookieExpiry = () => new Date(Date.now() + msIn12Hours).toUTCString(); +const setPublisherCookie = (token) => coreStorage.setCookie(publisherCookieName, JSON.stringify(token), getFutureCookieExpiry()); + +const makePrebidIdentityContainer = (token) => ({uid2: {id: token}}); +const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ + userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params}] }, debug, ...extraSettings +}); + +const initialToken = `initial-advertising-token`; +const legacyToken = 'legacy-advertising-token'; +const refreshedToken = 'refreshed-advertising-token'; +const makeUid2Token = (token = initialToken, shouldRefresh = false, expired = false) => ({ + advertising_token: token, + refresh_token: 'fake-refresh-token', + identity_expires: expired ? Date.now() - 1000 : Date.now() + 60 * 60 * 1000, + refresh_from: shouldRefresh ? Date.now() - 1000 : Date.now() + 60 * 1000, + refresh_expires: Date.now() + 24 * 60 * 60 * 1000, // 24 hours + refresh_response_key: 'wR5t6HKMfJ2r4J7fEGX9Gw==', +}); +const expectInitialToken = (bid) => expect(bid?.userId ?? {}).to.deep.include(makePrebidIdentityContainer(initialToken)); +const expectRefreshedToken = (bid) => expect(bid?.userId ?? {}).to.deep.include(makePrebidIdentityContainer(refreshedToken)); +const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); +const expectGlobalToHaveRefreshedIdentity = () => expect(getGlobal().getUserIds()).to.deep.include(makePrebidIdentityContainer(refreshedToken)); +const expectGlobalToHaveNoUid2 = () => expect(getGlobal().getUserIds()).to.not.haveOwnProperty('uid2'); +const expectLegacyToken = (bid) => expect(bid.userId).to.deep.include(makePrebidIdentityContainer(legacyToken)); +const expectNoLegacyToken = (bid) => expect(bid.userId).to.not.deep.include(makePrebidIdentityContainer(legacyToken)); +const expectModuleCookieEmptyOrMissing = () => expect(coreStorage.getCookie(moduleCookieName)).to.be.null; +const expectModuleCookieToContain = (initialIdentity, latestIdentity) => { + const cookie = JSON.parse(coreStorage.getCookie(moduleCookieName)); + if (initialIdentity) expect(cookie.originalToken.advertising_token).to.equal(initialIdentity); + if (latestIdentity) expect(cookie.latestToken.advertising_token).to.equal(latestIdentity); +} + +const apiUrl = 'https://prod.uidapi.com/v2/token/refresh'; +const headers = { 'Content-Type': 'application/json' }; +const makeSuccessResponseBody = () => btoa(JSON.stringify({ status: 'success', body: { ...makeUid2Token(), advertising_token: refreshedToken } })); +const configureUid2Response = (httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); +const configureUid2ApiSuccessResponse = () => configureUid2Response(200, makeSuccessResponseBody()); +const configureUid2ApiFailResponse = () => configureUid2Response(500, 'Error'); + +const respondAfterDelay = (delay) => new Promise((resolve) => setTimeout(() => { + server.respond(); + setTimeout(() => resolve()); +}, delay)); + +const runAuction = async () => { + const adUnits = [{ + code: 'adUnit-code', + mediaTypes: {banner: {}, native: {}}, + sizes: [[300, 200], [300, 600]], + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + }]; + return new Promise(function(resolve) { + requestBidsHook(function() { + resolve(adUnits[0].bids[0]); + }, {adUnits}); + }); +} + +// Runs the provided test twice - once with a successful API mock, once with one which returns a server error +const testApiSuccessAndFailure = (act, testDescription, failTestDescription, only = false) => { + const testFn = only ? it.only : it; + testFn(`API responds successfully: ${testDescription}`, async function() { + configureUid2ApiSuccessResponse(); + await act(true); + }); + testFn(`API responds with an error: ${failTestDescription ?? testDescription}`, async function() { + configureUid2ApiFailResponse(); + await act(false); + }); +} +describe(`UID2 module`, function () { + let suiteSandbox, testSandbox, timerSpy, fullTestTitle, restoreSubtleToUndefined = false; + before(function () { + timerSpy = configureTimerInterceptors(debugOutput); + hook.ready(); + uninstallGdprEnforcement(); + + suiteSandbox = sinon.sandbox.create(); + // I'm unable to find an authoritative source, but apparently subtle isn't available in some test stacks for security reasons. + // I've confirmed it's available in Firefox since v34 (it seems to be unavailable on BrowserStack in Firefox v106). + if (typeof window.crypto.subtle === 'undefined') { + restoreSubtleToUndefined = true; + window.crypto.subtle = { importKey: () => {}, decrypt: () => {} }; + } + suiteSandbox.stub(window.crypto.subtle, 'importKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'decrypt').callsFake((settings, key, data) => Promise.resolve(new Uint8Array([...settings.iv, ...data]))); + }); + + after(function () { + suiteSandbox.restore(); + timerSpy.restore(); + if (restoreSubtleToUndefined) window.crypto.subtle = undefined; + }); + + const getFullTestTitle = (test) => `${test.parent.title ? getFullTestTitle(test.parent) + ' | ' : ''}${test.title}`; + beforeEach(function () { + debugOutput(`----------------- START TEST ------------------`); + fullTestTitle = getFullTestTitle(this.test.ctx.currentTest); + debugOutput(fullTestTitle); + testSandbox = sinon.sandbox.create(); + testSandbox.stub(utils, 'logWarn'); + + init(config); + setSubmoduleRegistry([uid2IdSubmodule]); + }); + + afterEach(async function() { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + config.resetConfig(); + testSandbox.restore(); + if (timerSpy.timers.length > 0) { + if (clearTimersAfterEachTest) { + debugOutput(`Cancelling ${timerSpy.timers.length} still-active timers.`); + timerSpy.clearAllActiveTimers(); + } else { + debugOutput(`Waiting on ${timerSpy.timers.length} still-active timers...`, timerSpy.timers); + await timerSpy.waitAllActiveTimers(); + } + } + coreStorage.setCookie(moduleCookieName, '', expireCookieDate); + coreStorage.setCookie(publisherCookieName, '', expireCookieDate); + + debugOutput('----------------- END TEST ------------------'); + }); + + describe('Configuration', function() { + it('When no baseUrl is provided in config, the module calls the production endpoint', function() { + const uid2Token = makeUid2Token(initialToken, true, true); + config.setConfig(makePrebidConfig({uid2Token})); + expect(server.requests[0]?.url).to.have.string('https://prod.uidapi.com/'); + }); + + it('When a baseUrl is provided in config, the module calls the provided endpoint', function() { + const uid2Token = makeUid2Token(initialToken, true, true); + config.setConfig(makePrebidConfig({uid2Token, uid2ApiBase: 'https://operator-integ.uidapi.com'})); + expect(server.requests[0]?.url).to.have.string('https://operator-integ.uidapi.com/'); + }); + }); + + it('When a legacy value is provided directly in configuration, it is passed on', async function() { + const valueConfig = makePrebidConfig(); + valueConfig.userSync.userIds[0].value = {uid2: {id: legacyToken}} + config.setConfig(valueConfig); + const bid = await runAuction(); + + expectLegacyToken(bid); + }); + + // These tests cover 'legacy' cookies - i.e. cookies set with just the uid2 advertising token, which was how some previous integrations worked. + // Some users might still have this cookie, and the module should use that token if a newer one isn't provided. + // This should cover older integrations where the server is setting this legacy cookie and expecting the module to pass it on. + describe('When a legacy cookie exists', function () { + // Creates a test which sets the legacy cookie, configures the UID2 module with provided params, runs an + const createLegacyTest = function(params, bidAssertions) { + return async function() { + coreStorage.setCookie(moduleCookieName, legacyToken, getFutureCookieExpiry()); + config.setConfig(makePrebidConfig(params)); + + const bid = await runAuction(); + bidAssertions.forEach(function(assertion) { assertion(bid); }); + } + }; + + it('and a legacy config is used, it should provide the legacy cookie', + createLegacyTest(legacyConfigParams, [expectLegacyToken])); + it('and a server cookie config is used without a valid server cookie, it should provide the legacy cookie', + createLegacyTest(serverCookieConfigParams, [expectLegacyToken])); + it('and a server cookie is used with a valid server cookie, it should provide the server cookie', + async function() { setPublisherCookie(makeUid2Token()); await createLegacyTest(serverCookieConfigParams, [expectInitialToken, expectNoLegacyToken])(); }); + it('and a token is provided in config, it should provide the config token', + createLegacyTest({uid2Token: makeUid2Token()}, [expectInitialToken, expectNoLegacyToken])); + }); + + // This setup runs all of the functional tests with both types of config - the full token response in params, or a server cookie with the cookie name provided + let scenarios = [ + { + name: 'Token provided in config call', + setConfig: (token, extraConfig = {}) => config.setConfig(makePrebidConfig({uid2Token: token}, extraConfig)), + }, + { + name: 'Token provided in server-set cookie', + setConfig: (token, extraConfig) => { + setPublisherCookie(token); + config.setConfig(makePrebidConfig(serverCookieConfigParams, extraConfig)); + }, + } + ] + + scenarios.forEach(function(scenario) { + describe(scenario.name, function() { + describe(`When an expired token which can be refreshed is provided`, function() { + describe('When the refresh is available in time', function() { + testApiSuccessAndFailure(async function(apiSucceeds) { + scenario.setConfig(makeUid2Token(initialToken, true, true)); + respondAfterDelay(auctionDelayMs / 10); + const bid = await runAuction(); + + if (apiSucceeds) expectRefreshedToken(bid); + else expectNoIdentity(bid); + }, 'it should be used in the auction', 'the auction should have no uid2'); + + testApiSuccessAndFailure(async function(apiSucceeds) { + scenario.setConfig(makeUid2Token(initialToken, true, true)); + respondAfterDelay(auctionDelayMs / 10); + + await runAuction(); + if (apiSucceeds) { + expectModuleCookieToContain(initialToken, refreshedToken); + } else { + expectModuleCookieEmptyOrMissing(); + } + }, 'the refreshed token should be stored in the module cookie', 'the module cookie should not be set'); + }); + describe(`when the response doesn't arrive before the auction timer`, function() { + testApiSuccessAndFailure(async function() { + scenario.setConfig(makeUid2Token(initialToken, true, true)); + const bid = await runAuction(); + expectNoIdentity(bid); + }, 'it should run the auction'); + + testApiSuccessAndFailure(async function(apiSucceeds) { + scenario.setConfig(makeUid2Token(initialToken, true, true)); + const promise = respondAfterDelay(auctionDelayMs * 2); + + const bid = await runAuction(); + expectNoIdentity(bid); + expectGlobalToHaveNoUid2(); + await promise; + if (apiSucceeds) expectGlobalToHaveRefreshedIdentity(); + else expectGlobalToHaveNoUid2(); + }, 'it should update the userId after the auction', 'there should be no global identity'); + }) + describe('and there is a refreshed token in the module cookie', function() { + it('the refreshed value from the cookie is used', async function() { + const initialIdentity = makeUid2Token(initialToken, true, true); + const refreshedIdentity = makeUid2Token(refreshedToken); + const moduleCookie = {originalToken: initialIdentity, latestToken: refreshedIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), getFutureCookieExpiry()); + scenario.setConfig(initialIdentity); + + const bid = await runAuction(); + expectRefreshedToken(bid); + }); + }) + }); + + describe(`When a current token is provided`, function() { + beforeEach(function() { + scenario.setConfig(makeUid2Token()); + }); + + it('it should use the token in the auction', async function() { + const bid = await runAuction(); + expectInitialToken(bid); + }); + }); + + describe(`When a current token which should be refreshed is provided, and the auction is set to run immediately`, function() { + beforeEach(function() { + scenario.setConfig(makeUid2Token(initialToken, true), {auctionDelay: 0, syncDelay: 1}); + }); + testApiSuccessAndFailure(async function() { + respondAfterDelay(10); + const bid = await runAuction(); + expectInitialToken(bid); + }, 'it should not be refreshed before the auction runs'); + + testApiSuccessAndFailure(async function(success) { + const promise = respondAfterDelay(1); + await runAuction(); + await promise; + if (success) { + expectModuleCookieToContain(initialToken, refreshedToken); + } else { + expectModuleCookieToContain(initialToken, initialToken); + } + }, 'the refreshed token should be stored in the module cookie after the auction runs', 'the module cookie should only have the original token'); + + it('it should use the current token in the auction', async function() { + const bid = await runAuction(); + expectInitialToken(bid); + }); + }); + }); + }); +}); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index e6058673d41..bf27ef0ff81 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -9,7 +9,7 @@ import { setSubmoduleRegistry, syncDelay, PBJS_USER_ID_OPTOUT_NAME, - findRootDomain, + findRootDomain, requestDataDeletion, } from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; @@ -405,6 +405,9 @@ describe('User ID', function () { init(config); setSubmoduleRegistry([amxIdSubmodule, sharedIdSystemSubmodule, identityLinkSubmodule, imuIdSubmodule]); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + config.setConfig({ userSync: { ppid: 'pubcid.org', @@ -416,19 +419,52 @@ describe('User ID', function () { ] } }); - // before ppid should not be set - expect(window.googletag._ppid).to.equal(undefined); return expectImmediateBidHook(() => {}, {adUnits}).then(() => { // ppid should have been set without dashes and stuff expect(window.googletag._ppid).to.equal('pubCommonidvaluepubCommonidvalue'); }); }); + it('should set PPID when the source needs to call out to the network', () => { + let adUnits = [getAdUnitMock()]; + init(config); + const callback = sinon.stub(); + setSubmoduleRegistry([{ + name: 'sharedId', + getId: function () { + return {callback} + }, + decode(d) { + return d + } + }]); + config.setConfig({ + userSync: { + ppid: 'pubcid.org', + auctionDelay: 10, + userIds: [ + { + name: 'sharedId', + } + ] + } + }); + return expectImmediateBidHook(() => {}, {adUnits}).then(() => { + expect(window.googletag._ppid).to.be.undefined; + const uid = 'thismustbelongerthan32characters' + callback.yield({pubcid: uid}); + expect(window.googletag._ppid).to.equal(uid); + }); + }); + it('should set googletag ppid correctly for imuIdSubmodule', function () { let adUnits = [getAdUnitMock()]; init(config); setSubmoduleRegistry([imuIdSubmodule]); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + config.setConfig({ userSync: { ppid: 'ppid.intimatemerger.com', @@ -437,8 +473,7 @@ describe('User ID', function () { ] } }); - // before ppid should not be set - expect(window.googletag._ppid).to.equal(undefined); + return expectImmediateBidHook(() => {}, {adUnits}).then(() => { // ppid should have been set without dashes and stuff expect(window.googletag._ppid).to.equal('imppidvalueimppidvalueimppidvalue'); @@ -862,7 +897,7 @@ describe('User ID', function () { storage: {name: 'intentIqId', type: 'cookie'} }, { name: 'hadronId', - storage: {name: 'hadronId', type: 'cookie'} + storage: {name: 'hadronId', type: 'html5'} }, { name: 'zeotapIdPlus' }, { @@ -1263,7 +1298,7 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.amxId'); expect(bid.userId.amxId).to.equal('test_amxid_id'); expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'amxrtb.com', + source: 'amxdt.net', uids: [{ id: 'test_amxid_id', atype: 1, @@ -1837,8 +1872,8 @@ describe('User ID', function () { it('test hook from hadronId html5', function (done) { // simulate existing browser local storage values - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'random-ls-identifier'})); - localStorage.setItem('hadronId_exp', ''); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); init(config); setSubmoduleRegistry([hadronIdSubmodule]); @@ -1848,15 +1883,15 @@ describe('User ID', function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('random-ls-identifier'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'audigent.com', - uids: [{id: 'random-ls-identifier', atype: 1}] + uids: [{id: 'testHadronId1', atype: 1}] }); }); }); localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp', ''); + localStorage.removeItem('hadronId_exp'); done(); }, {adUnits}); }); @@ -2090,7 +2125,9 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + // hadronId only supports localStorage + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); @@ -2114,7 +2151,7 @@ describe('User ID', function () { ['netId', 'netId', 'cookie'], ['intentIqId', 'intentIqId', 'cookie'], ['zeotapIdPlus', 'IDP', 'cookie'], - ['hadronId', 'hadronId', 'cookie'], + ['hadronId', 'hadronId', 'html5'], ['criteo', 'storage_criteo', 'cookie'], ['mwOpenLinkId', 'mwol', 'cookie'], ['tapadId', 'tapad_id', 'cookie'], @@ -2157,7 +2194,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); // also check that criteo id was copied to bid expect(bid).to.have.deep.nested.property('userId.criteoId'); expect(bid.userId.criteoId).to.equal('test_bidid'); @@ -2196,7 +2233,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); @@ -2249,7 +2287,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('hadronId', JSON.stringify({'hadronId': 'testHadronId'}), (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); + localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); @@ -2285,7 +2324,7 @@ describe('User ID', function () { }, { name: 'zeotapIdPlus' }, { - name: 'hadronId', storage: {name: 'hadronId', type: 'cookie'} + name: 'hadronId', storage: {name: 'hadronId', type: 'html5'} }, { name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { @@ -2353,7 +2392,7 @@ describe('User ID', function () { expect(bid.userId.IDP).to.equal('zeotapId'); // also check that hadronId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId'); + expect(bid.userId.hadronId).to.equal('testHadronId1'); expect(bid.userId.uid2).to.deep.equal({ id: 'Sample_AD_Token' }); @@ -2385,7 +2424,8 @@ describe('User ID', function () { coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('hadronId', '', EXPIRED_COOKIE_DATE); + localStorage.removeItem('hadronId'); + localStorage.removeItem('hadronId_exp'); coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('deepintentId', '', EXPIRED_COOKIE_DATE); @@ -2668,45 +2708,70 @@ describe('User ID', function () { }); }); - describe('findRootDomain', function () { - let sandbox; + describe('requestDataDeletion', () => { + function idMod(name, value) { + return { + name, + getId() { + return {id: value} + }, + decode(d) { + return {[name]: d} + }, + onDataDeletionRequest: sinon.stub() + } + } + let mod1, mod2, mod3, cfg1, cfg2, cfg3; - beforeEach(function () { + beforeEach(() => { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule]); + mod1 = idMod('id1', 'val1'); + mod2 = idMod('id2', 'val2'); + mod3 = idMod('id3', 'val3'); + cfg1 = getStorageMock('id1', 'id1', 'cookie'); + cfg2 = getStorageMock('id2', 'id2', 'html5'); + cfg3 = {name: 'id3', value: {id3: 'val3'}}; + setSubmoduleRegistry([mod1, mod2, mod3]); config.setConfig({ + auctionDelay: 1, userSync: { - syncDelay: 0, - userIds: [ - { - name: 'pubCommonId', - value: { pubcid: '11111' }, - }, - ], - }, + userIds: [cfg1, cfg2, cfg3] + } }); - sandbox = sinon.createSandbox(); - sandbox - .stub(coreStorage, 'getCookie') - .onFirstCall() - .returns(null) // .co.uk - .onSecondCall() - .returns('writeable'); // realdomain.co.uk; - }); - - afterEach(function () { - sandbox.restore(); - }); - - it('should just find the root domain', function () { - var domain = findRootDomain('sub.realdomain.co.uk'); - expect(domain).to.be.eq('realdomain.co.uk'); - }); - - it('should find the full domain when no subdomain is present', function () { - var domain = findRootDomain('realdomain.co.uk'); - expect(domain).to.be.eq('realdomain.co.uk'); - }); + return getGlobal().refreshUserIds(); + }); + + it('deletes stored IDs', () => { + expect(coreStorage.getCookie('id1')).to.exist; + expect(coreStorage.getDataFromLocalStorage('id2')).to.exist; + requestDataDeletion(sinon.stub()); + expect(coreStorage.getCookie('id1')).to.not.exist; + expect(coreStorage.getDataFromLocalStorage('id2')).to.not.exist; + }); + + it('invokes onDataDeletionRequest', () => { + requestDataDeletion(sinon.stub()); + sinon.assert.calledWith(mod1.onDataDeletionRequest, cfg1, {id1: 'val1'}); + sinon.assert.calledWith(mod2.onDataDeletionRequest, cfg2, {id2: 'val2'}) + sinon.assert.calledWith(mod3.onDataDeletionRequest, cfg3, {id3: 'val3'}) + }); + + describe('does not choke when onDataDeletionRequest', () => { + Object.entries({ + 'is missing': () => { delete mod1.onDataDeletionRequest }, + 'throws': () => { mod1.onDataDeletionRequest.throws(new Error()) } + }).forEach(([t, setup]) => { + it(t, () => { + setup(); + const next = sinon.stub(); + const arg = {random: 'value'}; + requestDataDeletion(next, arg); + sinon.assert.calledOnce(mod2.onDataDeletionRequest); + sinon.assert.calledOnce(mod3.onDataDeletionRequest); + sinon.assert.calledWith(next, arg); + }) + }) + }) }); }); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index d30b9f31417..24d565805d3 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -13,10 +13,13 @@ import { getUniqueDealId, getNextDealId, getVidazooSessionId, + webSessionId } from 'modules/vidazooBidAdapter.js'; import * as utils from 'src/utils.js'; import {version} from 'package.json'; import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; const SUB_DOMAIN = 'openrtb'; @@ -37,20 +40,78 @@ const BID = { 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc' + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '1234567890' + } + } }; +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', 'gdprApplies': true }, + 'gppString': 'gpp_string', + 'gppSid': [7], 'uspConsent': 'consent_string', 'refererInfo': { 'page': 'https://www.greatsite.com', 'ref': 'https://www.somereferrer.com' - } + }, + 'ortb2': { + 'site': { + 'cat': ['IAB2'], + 'pagecat': ['IAB2-2'] + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + } + }, }; const SERVER_RESPONSE = { @@ -75,6 +136,23 @@ const SERVER_RESPONSE = { } }; +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['vidazoo.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +} + const REQUEST = { data: { width: 300, @@ -113,6 +191,11 @@ describe('VidazooBidAdapter', function () { it('exists and is a string', function () { expect(adapter.code).to.exist.and.to.be.a('string'); }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); }); describe('validate bid requests', function () { @@ -150,14 +233,83 @@ describe('VidazooBidAdapter', function () { before(function () { $$PREBID_GLOBAL$$.bidderSettings = { vidazoo: { - storageAllowed: true + storageAllowed: true, } }; sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1000); }); - it('should build request for each size', function () { + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + cat: ['IAB2'], + pagecat: ['IAB2-2'], + cb: 1000, + dealId: 1, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidderRequestId: '12a8ae9ada9c13', + gpid: '', + prebidVersion: version, + ptrace: '1000', + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sessionId: '', + sizes: ['545x307'], + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + isStorageAllowed: true, + webSessionId: webSessionId, + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }); + }); + + it('should build banner request for each size', function () { + config.setConfig({ + bidderTimeout: 3000 + }); const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -168,6 +320,15 @@ describe('VidazooBidAdapter', function () { gdprConsent: 'consent_string', gdpr: 1, usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', sizes: ['300x250', '300x600'], url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', @@ -176,16 +337,23 @@ describe('VidazooBidAdapter', function () { bidId: '2d52001cabd527', adUnitCode: 'div-gpt-ad-12345-0', publisherId: '59ac17c192832d0011283fe3', - dealId: 1, + dealId: 2, sessionId: '', uniqueDealId: `${hashUrl}_${Date.now().toString()}`, bidderVersion: adapter.version, prebidVersion: version, schain: BID.schain, + ptrace: '1000', res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + isStorageAllowed: true, + gpid: '1234567890', + cat: ['IAB2'], + pagecat: ['IAB2-2'], + webSessionId: webSessionId } }); }); @@ -195,6 +363,7 @@ describe('VidazooBidAdapter', function () { sandbox.restore(); }); }); + describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); @@ -239,7 +408,7 @@ describe('VidazooBidAdapter', function () { expect(responses).to.be.empty; }); - it('should return an array of interpreted responses', function () { + it('should return an array of interpreted banner responses', function () { const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); expect(responses).to.have.length(1); expect(responses[0]).to.deep.equal({ @@ -258,6 +427,26 @@ describe('VidazooBidAdapter', function () { }); }); + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['vidazoo.com'] + } + }); + }); + it('should take default TTL', function () { const serverResponse = utils.deepClone(SERVER_RESPONSE); delete serverResponse.body.results[0].exp; diff --git a/test/spec/modules/videoModule/adQueue_spec.js b/test/spec/modules/videoModule/adQueue_spec.js new file mode 100644 index 00000000000..4002e0b6dcc --- /dev/null +++ b/test/spec/modules/videoModule/adQueue_spec.js @@ -0,0 +1,172 @@ +import { expect } from 'chai'; +import { AdQueueCoordinator } from '../../../../modules/videoModule/adQueue.js'; +import { AD_BREAK_END, SETUP_COMPLETE } from '../../../../libraries/video/constants/events.js' + +const testId = 'testId'; +describe('Ad Queue Coordinator', function () { + const mockVideoCoreFactory = function () { + return { + onEvents: sinon.spy(), + offEvents: sinon.spy(), + setAdTagUrl: sinon.spy(), + } + }; + + const mockEventsFactory = function () { + return { + emit: sinon.spy() + }; + }; + + describe('Before Provider Setup Complete', function () { + it('should push ad to queue', function () { + const mockVideoCore = mockVideoCoreFactory(); + const mockEvents = mockEventsFactory(); + const coordinator = AdQueueCoordinator(mockVideoCore, mockEvents); + coordinator.registerProvider(testId); + coordinator.queueAd('testAdTag', testId, { param: {} }); + + expect(mockEvents.emit.calledOnce).to.be.true; + let emitArgs = mockEvents.emit.firstCall.args; + expect(emitArgs[0]).to.be.equal('videoAuctionAdLoadQueued'); + expect(mockVideoCore.setAdTagUrl.called).to.be.false; + }); + }); + + describe('After Provider Setup Complete', function () { + it('should load from ad queue', function () { + const mockVideoCore = mockVideoCoreFactory(); + const mockEvents = mockEventsFactory(); + let setupComplete; + mockVideoCore.onEvents = function(events, callback, id) { + if (events[0] === SETUP_COMPLETE && id === testId) { + setupComplete = callback; + } + }; + const coordinator = AdQueueCoordinator(mockVideoCore, mockEvents); + coordinator.registerProvider(testId); + coordinator.queueAd('testAdTag', testId, { param: {} }); + + expect(mockEvents.emit.calledOnce).to.be.true; + let emitArgs = mockEvents.emit.firstCall.args; + expect(emitArgs[0]).to.be.equal('videoAuctionAdLoadQueued'); + + setupComplete('', { divId: testId }); + expect(mockEvents.emit.calledTwice).to.be.true; + emitArgs = mockEvents.emit.secondCall.args; + expect(emitArgs[0]).to.be.equal('videoAuctionAdLoadAttempt'); + expect(mockVideoCore.setAdTagUrl.calledOnce).to.be.true; + }); + + it('should load ads without queueing', function () { + const mockVideoCore = mockVideoCoreFactory(); + const mockEvents = mockEventsFactory(); + let setupComplete; + mockVideoCore.onEvents = function(events, callback, id) { + if (events[0] === SETUP_COMPLETE && id === testId) { + setupComplete = callback; + } + }; + const coordinator = AdQueueCoordinator(mockVideoCore, mockEvents); + coordinator.registerProvider(testId); + + setupComplete('', { divId: testId }); + + coordinator.queueAd('testAdTag', testId, { param: {} }); + expect(mockEvents.emit.calledOnce).to.be.true; + let emitArgs = mockEvents.emit.firstCall.args; + expect(emitArgs[0]).to.be.equal('videoAuctionAdLoadAttempt'); + expect(mockVideoCore.setAdTagUrl.calledOnce).to.be.true; + }); + }); + + describe('On Ad Break End', function () { + it('should load from queue', function () { + const mockVideoCore = mockVideoCoreFactory(); + const mockEvents = mockEventsFactory(); + let setupComplete; + let adBreakEnd; + + mockVideoCore.onEvents = function(events, callback, id) { + if (events[0] === SETUP_COMPLETE && id === testId) { + setupComplete = callback; + } + + if (events[0] === AD_BREAK_END && id === testId) { + adBreakEnd = callback; + } + }; + + const coordinator = AdQueueCoordinator(mockVideoCore, mockEvents); + coordinator.registerProvider(testId); + coordinator.queueAd('testAdTag', testId); + coordinator.queueAd('testAdTag2', testId); + coordinator.queueAd('testAdTag3', testId); + + mockEvents.emit.resetHistory(); + + setupComplete('', { divId: testId }); + + expect(mockEvents.emit.calledOnce).to.be.true; + let emitArgs = mockEvents.emit.firstCall.args; + expect(emitArgs[0]).to.be.equal('videoAuctionAdLoadAttempt'); + expect(mockVideoCore.setAdTagUrl.calledOnce).to.be.true; + let setAdTagArgs = mockVideoCore.setAdTagUrl.firstCall.args; + expect(setAdTagArgs[0]).to.be.equal('testAdTag'); + + adBreakEnd('', { divId: testId }); + + expect(mockEvents.emit.calledTwice).to.be.true; + emitArgs = mockEvents.emit.secondCall.args; + expect(emitArgs[0]).to.be.equal('videoAuctionAdLoadAttempt'); + expect(mockVideoCore.setAdTagUrl.calledTwice).to.be.true; + setAdTagArgs = mockVideoCore.setAdTagUrl.secondCall.args; + expect(setAdTagArgs[0]).to.be.equal('testAdTag2'); + + adBreakEnd('', { divId: testId }); + + expect(mockEvents.emit.calledThrice).to.be.true; + emitArgs = mockEvents.emit.thirdCall.args; + expect(emitArgs[0]).to.be.equal('videoAuctionAdLoadAttempt'); + expect(mockVideoCore.setAdTagUrl.calledThrice).to.be.true; + setAdTagArgs = mockVideoCore.setAdTagUrl.thirdCall.args; + expect(setAdTagArgs[0]).to.be.equal('testAdTag3'); + + adBreakEnd('', { divId: testId }); + + expect(mockEvents.emit.calledThrice).to.be.true; + expect(mockVideoCore.setAdTagUrl.calledThrice).to.be.true; + }); + + it('should stop responding to AdBreakEnd when queue is empty', function () { + const mockVideoCore = mockVideoCoreFactory(); + let setupComplete; + let adBreakEnd; + + mockVideoCore.onEvents = function(events, callback, id) { + if (events[0] === SETUP_COMPLETE && id === testId) { + setupComplete = callback; + } + + if (events[0] === AD_BREAK_END && id === testId) { + adBreakEnd = callback; + } + }; + + const coordinator = AdQueueCoordinator(mockVideoCore, mockEventsFactory()); + coordinator.registerProvider(testId); + coordinator.queueAd('testAdTag', testId); + coordinator.queueAd('testAdTag2', testId); + coordinator.queueAd('testAdTag3', testId); + + setupComplete('', { divId: testId }); + adBreakEnd('', { divId: testId }); + adBreakEnd('', { divId: testId }); + expect(mockVideoCore.setAdTagUrl.calledThrice).to.be.true; + adBreakEnd('', { divId: testId }); + expect(mockVideoCore.setAdTagUrl.calledThrice).to.be.true; + adBreakEnd('', { divId: testId }); + expect(mockVideoCore.setAdTagUrl.calledThrice).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/videoModule/coreVideo_spec.js b/test/spec/modules/videoModule/coreVideo_spec.js new file mode 100644 index 00000000000..17c6e3811e0 --- /dev/null +++ b/test/spec/modules/videoModule/coreVideo_spec.js @@ -0,0 +1,98 @@ +import { expect } from 'chai'; +import { VideoCore } from 'modules/videoModule/coreVideo.js'; + +describe('Video Core', function () { + const mockSubmodule = { + getOrtbVideo: sinon.spy(), + getOrtbContent: sinon.spy(), + setAdTagUrl: sinon.spy(), + onEvent: sinon.spy(), + offEvent: sinon.spy(), + }; + + const otherSubmodule = { + getOrtbVideo: () => {}, + getOrtbContent: () => {}, + setAdTagUrl: () => {}, + onEvent: () => {}, + offEvent: () => {}, + }; + + const testId = 'test_id'; + const testVendorCode = 0; + const otherId = 'other_id'; + const otherVendorCode = 1; + + const parentModuleMock = { + registerSubmodule: sinon.spy(), + getSubmodule: sinon.spy(id => { + if (id === testId) { + return mockSubmodule; + } else if (id === otherId) { + return otherSubmodule; + } + }) + }; + + const videoCore = VideoCore(parentModuleMock); + + videoCore.registerProvider({ + vendorCode: testVendorCode, + divId: testId + }); + + videoCore.registerProvider({ + vendorCode: otherVendorCode, + divId: otherId + }); + + describe('registerProvider', function () { + it('should delegate the registration to the Parent Module', function () { + expect(parentModuleMock.registerSubmodule.calledTwice).to.be.true; + expect(parentModuleMock.registerSubmodule.args[0][0]).to.be.equal(testId); + expect(parentModuleMock.registerSubmodule.args[1][0]).to.be.equal(otherId); + expect(parentModuleMock.registerSubmodule.args[0][1]).to.be.equal(testVendorCode); + expect(parentModuleMock.registerSubmodule.args[1][1]).to.be.equal(otherVendorCode); + }); + }); + + describe('getOrtbVideo', function () { + it('delegates to the submodule of the right divId', function () { + videoCore.getOrtbVideo(testId); + videoCore.getOrtbVideo(otherId); + expect(mockSubmodule.getOrtbVideo.calledOnce).to.be.true; + }); + }); + + describe('getOrtbContent', function () { + it('delegates to the submodule of the right divId', function () { + videoCore.getOrtbContent(testId); + videoCore.getOrtbContent(otherId); + expect(mockSubmodule.getOrtbContent.calledOnce).to.be.true; + }); + }); + + describe('setAdTagUrl', function () { + it('delegates to the submodule of the right divId', function () { + videoCore.setAdTagUrl('', testId); + videoCore.setAdTagUrl('', otherId); + expect(mockSubmodule.setAdTagUrl.calledOnce).to.be.true; + }); + }); + + describe('onEvents', function () { + it('delegates to the submodule of the right divId', function () { + videoCore.onEvents(['event'], () => {}, testId); + videoCore.onEvents(['event'], () => {}, otherId); + expect(mockSubmodule.onEvent.calledOnce).to.be.true; + }); + }); + + describe('offEvents', function () { + it('delegates to the submodule of the right divId', function () { + videoCore.offEvents(['event'], () => {}, testId); + videoCore.offEvents(['event'], () => {}, otherId); + expect(mockSubmodule.offEvent.calledOnce).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/videoModule/pbVideo_spec.js b/test/spec/modules/videoModule/pbVideo_spec.js new file mode 100644 index 00000000000..41780a007dd --- /dev/null +++ b/test/spec/modules/videoModule/pbVideo_spec.js @@ -0,0 +1,374 @@ +import { expect } from 'chai'; +import { PbVideo } from 'modules/videoModule'; +import CONSTANTS from 'src/constants.json'; + +let ortbVideoMock; +let ortbContentMock; +let videoCoreMock; +let getConfigMock; +let requestBidsMock; +let pbGlobalMock; +let pbEventsMock; +let videoEventsMock; +let gamSubmoduleMock; +let gamSubmoduleFactoryMock; +let videoImpressionVerifierFactoryMock; +let videoImpressionVerifierMock; +let adQueueCoordinatorMock; +let adQueueCoordinatorFactoryMock; + +function resetTestVars() { + ortbVideoMock = {}; + ortbContentMock = {}; + videoCoreMock = { + registerProvider: sinon.spy(), + initProvider: sinon.spy(), + onEvents: sinon.spy(), + getOrtbVideo: () => ortbVideoMock, + getOrtbContent: () => ortbContentMock, + setAdTagUrl: sinon.spy() + }; + getConfigMock = () => {}; + requestBidsMock = { + before: sinon.spy() + }; + pbGlobalMock = { + requestBids: requestBidsMock, + getHighestCpmBids: sinon.spy(), + getBidResponsesForAdUnitCode: sinon.spy(), + setConfig: sinon.spy(), + getConfig: () => ({}), + markWinningBidAsUsed: sinon.spy() + }; + pbEventsMock = { + emit: sinon.spy(), + on: sinon.spy() + }; + videoEventsMock = []; + gamSubmoduleMock = { + getAdTagUrl: sinon.spy() + }; + + gamSubmoduleFactoryMock = sinon.spy(() => gamSubmoduleMock); + + videoImpressionVerifierMock = { + trackBid: sinon.spy(), + getBidIdentifiers: sinon.spy() + }; + + videoImpressionVerifierFactoryMock = () => videoImpressionVerifierMock; + + adQueueCoordinatorMock = { + registerProvider: sinon.spy(), + queueAd: sinon.spy() + }; + + adQueueCoordinatorFactoryMock = () => adQueueCoordinatorMock; +} + +let pbVideoFactory = (videoCore, getConfig, pbGlobal, pbEvents, videoEvents, gamSubmoduleFactory, videoImpressionVerifierFactory, adQueueCoordinator) => { + const pbVideo = PbVideo( + videoCore || videoCoreMock, + getConfig || getConfigMock, + pbGlobal || pbGlobalMock, + pbEvents || pbEventsMock, + videoEvents || videoEventsMock, + gamSubmoduleFactory || gamSubmoduleFactoryMock, + videoImpressionVerifierFactory || videoImpressionVerifierFactoryMock, + adQueueCoordinator || adQueueCoordinatorMock + ); + pbVideo.init(); + return pbVideo; +} + +describe('Prebid Video', function () { + beforeEach(() => resetTestVars()); + + describe('Setting video to config', function () { + let providers = [{ divId: 'div1' }, { divId: 'div2' }]; + let getConfigCallback; + let getConfig = (propertyName, callback) => { + if (propertyName === 'video') { + getConfigCallback = callback; + } + }; + + beforeEach(() => { + pbVideoFactory(null, getConfig); + getConfigCallback({ video: { providers } }); + }); + + it('Should register providers', function () { + expect(videoCoreMock.registerProvider.calledTwice).to.be.true; + }); + + it('Should register events', function () { + expect(videoCoreMock.onEvents.calledTwice).to.be.true; + const onEventsSpy = videoCoreMock.onEvents; + expect(onEventsSpy.getCall(0).args[2]).to.be.equal('div1'); + expect(onEventsSpy.getCall(1).args[2]).to.be.equal('div2'); + }); + + describe('Event triggering', function () { + it('Should emit events off of Prebid\'s Events', function () { + let eventHandler; + const videoCore = Object.assign({}, videoCoreMock, { + onEvents: (events, eventHandler_) => eventHandler = eventHandler_ + }); + pbVideoFactory(videoCore, getConfig); + getConfigCallback({ video: { providers } }); + const expectedType = 'test_event'; + const expectedPayload = {'test': 'data'}; + eventHandler(expectedType, expectedPayload); + expect(pbEventsMock.emit.calledOnce).to.be.true; + expect(pbEventsMock.emit.getCall(0).args[0]).to.be.equal('video' + expectedType.replace(/^./, expectedType[0].toUpperCase())); + expect(pbEventsMock.emit.getCall(0).args[1]).to.be.equal(expectedPayload); + }); + }); + + describe('Ad Server configuration', function() { + const test_vendor_code = 5; + const test_params = { test: 'params' }; + providers[0].adServer = { vendorCode: test_vendor_code, params: test_params }; + + it('should instantiate the GAM Submodule', function () { + expect(gamSubmoduleFactoryMock.calledOnce).to.be.true; + }); + }); + }); + + describe('Ad unit Enrichment', function () { + it('registers before:bidRequest hook', function () { + pbVideoFactory(); + expect(requestBidsMock.before.calledOnce).to.be.true; + }); + + it('requests oRtb params and writes them to ad unit and config', function() { + const getOrtbVideoSpy = videoCoreMock.getOrtbVideo = sinon.spy(() => ({ + test: 'videoTestValue' + })); + const getOrtbContentSpy = videoCoreMock.getOrtbContent = sinon.spy(() => ({ + test: 'contentTestValue' + })); + + let beforeBidRequestCallback; + const requestBids = { + before: callback_ => beforeBidRequestCallback = callback_ + }; + + pbVideoFactory(null, null, Object.assign({}, pbGlobalMock, { requestBids })); + expect(beforeBidRequestCallback).to.not.be.undefined; + const nextFn = sinon.spy(); + const adUnits = [{ + code: 'ad1', + mediaTypes: { + video: {} + }, + video: { divId: 'divId' } + }]; + beforeBidRequestCallback(nextFn, { adUnits }); + expect(getOrtbVideoSpy.calledOnce).to.be.true; + expect(getOrtbContentSpy.calledOnce).to.be.true; + const adUnit = adUnits[0]; + expect(adUnit.mediaTypes.video).to.have.property('test', 'videoTestValue'); + expect(nextFn.calledOnce).to.be.true; + expect(nextFn.getCall(0).args[0].ortb2).to.be.deep.equal({ site: { content: { test: 'contentTestValue' } } }); + }); + }); + + describe('Ad tag injection', function () { + let auctionEndCallback; + let providers = [{ divId: 'div1', adServer: {} }, { divId: 'div2' }]; + let getConfig = (propertyName, callbackFn) => { + if (propertyName === 'video') { + if (callbackFn) { + callbackFn({ video: { providers } }); + } else { + return { providers }; + } + } + }; + + const pbEvents = { + emit: () => {}, + on: (event, callback) => { + if (event === CONSTANTS.EVENTS.AUCTION_END) { + auctionEndCallback = callback + } + }, + off: () => {} + }; + + const expectedVendorCode = 5; + const expectedAdTag = 'test_tag'; + const expectedAdUnitCode = 'expectedAdUnitcode'; + const expectedDivId = 'expectedDivId'; + const expectedAdUnit = { + code: expectedAdUnitCode, + video: { + divId: expectedDivId, + adServer: { + vendorCode: expectedVendorCode, + baseAdTagUrl: expectedAdTag + } + } + }; + const auctionResults = { adUnits: [ expectedAdUnit, {} ] }; + + beforeEach(() => { + gamSubmoduleMock.getAdTagUrl.resetHistory(); + videoCoreMock.setAdTagUrl.resetHistory(); + adQueueCoordinatorMock.queueAd.resetHistory(); + }); + + let beforeBidRequestCallback; + const requestBids = { + before: callback_ => beforeBidRequestCallback = callback_ + }; + + it('should request ad tag url from adServer when configured to use adServer', function () { + const expectedVastUrl = 'expectedVastUrl'; + const expectedVastXml = 'expectedVastXml'; + const pbGlobal = Object.assign({}, pbGlobalMock, { + requestBids, + getHighestCpmBids: () => [{ + vastUrl: expectedVastUrl, + vastXml: expectedVastXml + }, {}, {}, {}] + }); + pbVideoFactory(null, getConfig, pbGlobal, pbEvents); + + beforeBidRequestCallback(() => {}, {}); + auctionEndCallback(auctionResults); + expect(gamSubmoduleMock.getAdTagUrl.calledOnce).to.be.true; + expect(gamSubmoduleMock.getAdTagUrl.getCall(0).args[0]).is.equal(expectedAdUnit); + expect(gamSubmoduleMock.getAdTagUrl.getCall(0).args[1]).is.equal(expectedAdTag); + }); + + it('should load ad tag when ad server returns ad tag', function () { + const expectedAdTag = 'resulting ad tag'; + const gamSubmoduleFactory = () => ({ + getAdTagUrl: () => expectedAdTag + }); + const expectedVastUrl = 'expectedVastUrl'; + const expectedVastXml = 'expectedVastXml'; + const pbGlobal = Object.assign({}, pbGlobalMock, { + requestBids, + getHighestCpmBids: () => [{ + vastUrl: expectedVastUrl, + vastXml: expectedVastXml + }, {}, {}, {}] + }); + pbVideoFactory(null, getConfig, pbGlobal, pbEvents, null, gamSubmoduleFactory); + beforeBidRequestCallback(() => {}, {}); + auctionEndCallback(auctionResults); + expect(adQueueCoordinatorMock.queueAd.calledOnce).to.be.true; + expect(adQueueCoordinatorMock.queueAd.args[0][0]).to.be.equal(expectedAdTag); + expect(adQueueCoordinatorMock.queueAd.args[0][1]).to.be.equal(expectedDivId); + expect(adQueueCoordinatorMock.queueAd.args[0][2]).to.have.property('adUnitCode', expectedAdUnitCode); + }); + + it('should load ad tag from highest bid when ad server is not configured', function () { + const expectedVastUrl = 'expectedVastUrl'; + const expectedVastXml = 'expectedVastXml'; + const pbGlobal = Object.assign({}, pbGlobalMock, { + requestBids, + getHighestCpmBids: () => [{ + vastUrl: expectedVastUrl, + vastXml: expectedVastXml + }, {}, {}, {}] + }); + const expectedAdUnit = { + code: expectedAdUnitCode, + video: { divId: expectedDivId } + }; + const auctionResults = { adUnits: [ expectedAdUnit, {} ] }; + + pbVideoFactory(null, () => ({ providers: [] }), pbGlobal, pbEvents); + beforeBidRequestCallback(() => {}, {}); + auctionEndCallback(auctionResults); + expect(adQueueCoordinatorMock.queueAd.calledOnce).to.be.true; + expect(adQueueCoordinatorMock.queueAd.args[0][0]).to.be.equal(expectedVastUrl); + expect(adQueueCoordinatorMock.queueAd.args[0][1]).to.be.equal(expectedDivId); + expect(adQueueCoordinatorMock.queueAd.args[0][2]).to.have.property('adUnitCode', expectedAdUnitCode); + expect(adQueueCoordinatorMock.queueAd.args[0][2]).to.have.property('adXml', expectedVastXml); + }); + }); + + describe('Ad tracking', function () { + const expectedAdEventPayload = { adEventPayloadMarker: 'marker' }; + const expectedBid = { bidMarker: 'marker' }; + let bidAdjustmentCb; + let adImpressionCb; + let adErrorCb; + + const pbEvents = { + on: (event, callback) => { + if (event === CONSTANTS.EVENTS.BID_ADJUSTMENT) { + bidAdjustmentCb = callback; + } else if (event === 'videoAdImpression') { + adImpressionCb = callback; + } else if (event === 'videoAdError') { + adErrorCb = callback; + } + }, + emit: sinon.spy() + }; + + it('should ask Impression Verifier to track bid on Bid Adjustment', function () { + pbVideoFactory(null, null, null, pbEvents); + bidAdjustmentCb(); + expect(videoImpressionVerifierMock.trackBid.calledOnce).to.be.true; + }); + + it('should trigger video bid impression when the bid matched', function () { + pbEvents.emit.resetHistory(); + const pbGlobal = Object.assign({}, pbGlobalMock, { getBidResponsesForAdUnitCode: () => ({ bids: [expectedBid] }) }); + const videoImpressionVerifier = Object.assign({}, videoImpressionVerifierMock, { getBidIdentifiers: () => ({}) }); + pbVideoFactory(null, null, pbGlobal, pbEvents, null, null, () => videoImpressionVerifier); + adImpressionCb(expectedAdEventPayload); + + expect(pbEvents.emit.calledOnce).to.be.true; + expect(pbEvents.emit.getCall(0).args[0]).to.be.equal('videoBidImpression'); + const payload = pbEvents.emit.getCall(0).args[1]; + expect(payload.bid).to.be.equal(expectedBid); + expect(payload.adEvent).to.be.equal(expectedAdEventPayload); + expect(pbGlobal.markWinningBidAsUsed.calledOnce).to.be.true; + }); + + it('should trigger video bid error when the bid matched', function () { + pbEvents.emit.resetHistory(); + const pbGlobal = Object.assign({}, pbGlobalMock, { getBidResponsesForAdUnitCode: () => ({ bids: [expectedBid] }) }); + const videoImpressionVerifier = Object.assign({}, videoImpressionVerifierMock, { getBidIdentifiers: () => ({}) }); + pbVideoFactory(null, null, pbGlobal, pbEvents, null, null, () => videoImpressionVerifier); + adErrorCb(expectedAdEventPayload); + + expect(pbEvents.emit.calledOnce).to.be.true; + expect(pbEvents.emit.getCall(0).args[0]).to.be.equal('videoBidError'); + const payload = pbEvents.emit.getCall(0).args[1]; + expect(payload.bid).to.be.equal(expectedBid); + expect(payload.adEvent).to.be.equal(expectedAdEventPayload); + expect(pbGlobal.markWinningBidAsUsed.calledOnce).to.be.true; + }); + + it('should not trigger a bid impression when the bid did not match', function () { + pbEvents.emit.resetHistory(); + const pbGlobal = Object.assign({}, pbGlobalMock, { getBidResponsesForAdUnitCode: () => ({ bids: [expectedBid] }) }); + const videoImpressionVerifier = Object.assign({}, videoImpressionVerifierMock, { getBidIdentifiers: () => ({ auctionId: 'id' }) }); + pbVideoFactory(null, null, pbGlobal, pbEvents, null, null, () => videoImpressionVerifier); + adImpressionCb(expectedAdEventPayload); + + expect(pbEvents.emit.called).to.be.false; + }); + + it('should not trigger a bid error when the bid did not match', function () { + pbEvents.emit.resetHistory(); + const pbGlobal = Object.assign({}, pbGlobalMock, { getBidResponsesForAdUnitCode: () => ({ bids: [expectedBid] }) }); + const videoImpressionVerifier = Object.assign({}, videoImpressionVerifierMock, { getBidIdentifiers: () => ({ auctionId: 'id' }) }); + pbVideoFactory(null, null, pbGlobal, pbEvents, null, null, () => videoImpressionVerifier); + adErrorCb(expectedAdEventPayload); + + expect(pbEvents.emit.called).to.be.false; + }); + }); +}); diff --git a/test/spec/modules/videoModule/shared/parentModule_spec.js b/test/spec/modules/videoModule/shared/parentModule_spec.js new file mode 100644 index 00000000000..e3e4cfb7f3f --- /dev/null +++ b/test/spec/modules/videoModule/shared/parentModule_spec.js @@ -0,0 +1,72 @@ +import { SubmoduleBuilder, ParentModule } from 'libraries/video/shared/parentModule.js'; +import { expect } from 'chai'; + +describe('Parent Module', function() { + const idForMock = 0; + const vendorCodeForMock = 'a'; + const unrecognizedId = 999; + const unrecognizedVendorCode = 'zzz'; + const mockSubmodule = { test: 'test' }; + const mockSubmoduleBuilder = { + build: vendorCode => { + if (vendorCode === vendorCodeForMock) { + return mockSubmodule; + } else { + throw new Error('flawed'); + } + } + }; + const parentModule = ParentModule(mockSubmoduleBuilder); + + describe('Register Submodule', function () { + it('should throw when the builder fails to build', function () { + expect(() => parentModule.registerSubmodule(unrecognizedId, unrecognizedVendorCode)).to.throw('flawed'); + }); + }); + + describe('Get Submodule', function () { + it('should return registered submodules', function () { + parentModule.registerSubmodule(idForMock, vendorCodeForMock); + const submodule = parentModule.getSubmodule(idForMock); + expect(submodule).to.be.equal(mockSubmodule); + }); + + it('should return undefined when submodule is not registered', function () { + const submodule = parentModule.getSubmodule(unrecognizedId); + expect(submodule).to.be.undefined; + }); + }) +}); + +describe('Submodule Builder', function () { + const vendorCode1 = 1; + const vendorCode2 = 2; + const submodule1 = {}; + const initSpy = sinon.spy(); + const submodule2 = { init: initSpy }; + const submoduleFactory1 = () => submodule1; + const submoduleFactory2 = () => submodule2; + const submoduleFactory1Spy = sinon.spy(submoduleFactory1); + + const vendorDirectory = {}; + vendorDirectory[vendorCode1] = submoduleFactory1Spy; + vendorDirectory[vendorCode2] = submoduleFactory2; + + const submoduleBuilder = SubmoduleBuilder(vendorDirectory); + + it('should call submodule factory when vendor code is supported', function () { + const submodule = submoduleBuilder.build(vendorCode1); + expect(submoduleFactory1Spy.calledOnce).to.be.true; + expect(submodule).to.be.equal(submodule1); + }); + + it('should instantiate the submodule, when supported', function () { + const submodule = submoduleBuilder.build(vendorCode2); + expect(submodule).to.be.equal(submodule2); + }); + + it('should throw when vendor code is not recognized', function () { + const unrecognizedVendorCode = 999; + expect(() => submoduleBuilder.build(unrecognizedVendorCode)).to.throw('Unrecognized submodule vendor code: ' + unrecognizedVendorCode); + }); +}); diff --git a/test/spec/modules/videoModule/shared/state_spec.js b/test/spec/modules/videoModule/shared/state_spec.js new file mode 100644 index 00000000000..94f3cb73411 --- /dev/null +++ b/test/spec/modules/videoModule/shared/state_spec.js @@ -0,0 +1,26 @@ +import stateFactory from 'libraries/video/shared/state.js'; +import { expect } from 'chai'; + +describe('State', function () { + let state = stateFactory(); + beforeEach(() => { + state.clearState(); + }); + + it('should update state', function () { + state.updateState({ 'test': 'a' }); + expect(state.getState()).to.have.property('test', 'a'); + state.updateState({ 'test': 'b' }); + expect(state.getState()).to.have.property('test', 'b'); + state.updateState({ 'test_2': 'c' }); + expect(state.getState()).to.have.property('test', 'b'); + expect(state.getState()).to.have.property('test_2', 'c'); + }); + + it('should clear state', function () { + state.updateState({ 'test': 'a' }); + state.clearState(); + expect(state.getState()).to.not.have.property('test', 'a'); + expect(state.getState()).to.be.empty; + }); +}); diff --git a/test/spec/modules/videoModule/shared/vastXmlBuilder_spec.js b/test/spec/modules/videoModule/shared/vastXmlBuilder_spec.js new file mode 100644 index 00000000000..2c67b898a53 --- /dev/null +++ b/test/spec/modules/videoModule/shared/vastXmlBuilder_spec.js @@ -0,0 +1,103 @@ +import { buildVastWrapper, getVastNode, getAdNode, getWrapperNode, getAdSystemNode, + getAdTagUriNode, getErrorNode, getImpressionNode } from 'libraries/video/shared/vastXmlBuilder.js'; +import { expect } from 'chai'; + +describe('buildVastWrapper', function () { + it('should include impression and error nodes when requested', function () { + const vastXml = buildVastWrapper( + 'adId123', + 'http://wwww.testUrl.com/redirectUrl.xml', + 'http://wwww.testUrl.com/impression.jpg', + 'impressionId123', + 'http://wwww.testUrl.com/error.jpg' + ); + expect(vastXml).to.be.equal(`Prebid org`); + }); + + it('should omit error nodes when excluded', function () { + const vastXml = buildVastWrapper( + 'adId123', + 'http://wwww.testUrl.com/redirectUrl.xml', + 'http://wwww.testUrl.com/impression.jpg', + 'impressionId123', + ); + expect(vastXml).to.be.equal(`Prebid org`); + }); + + it('should omit impression nodes when excluded', function () { + const vastXml = buildVastWrapper( + 'adId123', + 'http://wwww.testUrl.com/redirectUrl.xml', + ); + expect(vastXml).to.be.equal(`Prebid org`); + }); +}); + +describe('getVastNode', function () { + it('should return well formed Vast node', function () { + const vastNode = getVastNode('body', '4.0'); + expect(vastNode).to.be.equal('body'); + }); + + it('should omit version when missing', function() { + const vastNode = getVastNode('body'); + expect(vastNode).to.be.equal('body'); + }); +}); + +describe('getAdNode', function () { + it('should return well formed Ad node', function () { + const adNode = getAdNode('body', 'adId123'); + expect(adNode).to.be.equal('body'); + }); + + it('should omit id when missing', function() { + const adNode = getAdNode('body'); + expect(adNode).to.be.equal('body'); + }); +}); + +describe('getWrapperNode', function () { + it('should return well formed Wrapper node', function () { + const wrapperNode = getWrapperNode('body'); + expect(wrapperNode).to.be.equal('body'); + }); +}); + +describe('getAdSystemNode', function () { + it('should return well formed AdSystem node', function () { + const adSystemNode = getAdSystemNode('testSysName', '5.0'); + expect(adSystemNode).to.be.equal('testSysName'); + }); + + it('should omit version when missing', function() { + const adSystemNode = getAdSystemNode('testSysName'); + expect(adSystemNode).to.be.equal('testSysName'); + }); +}); + +describe('getAdTagUriNode', function () { + it('should return well formed ad tag URI node', function () { + const adTagNode = getAdTagUriNode('http://wwww.testUrl.com/ad.xml'); + expect(adTagNode).to.be.equal(''); + }); +}); + +describe('getImpressionNode', function () { + it('should return well formed Impression node', function () { + const impressionNode = getImpressionNode('http://wwww.testUrl.com/adImpression.jpg', 'impresionId123'); + expect(impressionNode).to.be.equal(''); + }); + + it('should omit id when missing', function() { + const impressionNode = getImpressionNode('http://wwww.testUrl.com/adImpression.jpg'); + expect(impressionNode).to.be.equal(''); + }); +}); + +describe('getErrorNode', function () { + it('should return well formed Error node', function () { + const errorNode = getErrorNode('http://wwww.testUrl.com/adError.jpg'); + expect(errorNode).to.be.equal(''); + }); +}); diff --git a/test/spec/modules/videoModule/shared/vastXmlEditor_spec.js b/test/spec/modules/videoModule/shared/vastXmlEditor_spec.js new file mode 100644 index 00000000000..2304b2f2833 --- /dev/null +++ b/test/spec/modules/videoModule/shared/vastXmlEditor_spec.js @@ -0,0 +1,209 @@ +import { vastXmlEditorFactory } from 'libraries/video/shared/vastXmlEditor.js'; +import { expect } from 'chai'; + +describe('Vast XML Editor', function () { + const adWrapperXml = ` + + + + Prebid org + + + + +`; + + const inlineXml = ` + + + + Prebid org + Random Title + + + +`; + + const inLineWithWrapper = ` + + + + Prebid org + + + + + + Prebid org + Random Title + + + +`; + + const vastXmlEditor = vastXmlEditorFactory(); + const expectedImpressionUrl = 'https://test.impression.com/ping.gif'; + const expectedImpressionId = 'test-impression-id'; + const expectedErrorUrl = 'https://test.error.com/ping.gif'; + + it('should add Impression Nodes to the Ad Wrapper', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(adWrapperXml, null, expectedImpressionUrl, expectedImpressionId); + const expectedXml = ` + + + Prebid org + + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Impression Nodes to the InLine', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(inlineXml, null, expectedImpressionUrl, expectedImpressionId); + const expectedXml = ` + + + Prebid org + Random Title + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Impression Nodes to the Ad Wrapper and Inline', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(inLineWithWrapper, null, expectedImpressionUrl, expectedImpressionId); + const expectedXml = ` + + + Prebid org + + + + + + Prebid org + Random Title + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Error Nodes to the Ad Wrapper', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(adWrapperXml, null, null, null, expectedErrorUrl); + const expectedXml = ` + + + Prebid org + + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Error Nodes to the InLine', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(inlineXml, null, null, null, expectedErrorUrl); + const expectedXml = ` + + + Prebid org + Random Title + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Error Nodes to the Ad Wrapper and Inline', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(inLineWithWrapper, null, null, null, expectedErrorUrl); + const expectedXml = ` + + + Prebid org + + + + + + Prebid org + Random Title + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Impression Nodes and Error Nodes to the Ad Wrapper', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(adWrapperXml, null, expectedImpressionUrl, expectedImpressionId, expectedErrorUrl); + const expectedXml = ` + + + Prebid org + + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Impression Nodes and Error Nodes to the InLine', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(inlineXml, null, expectedImpressionUrl, expectedImpressionId, expectedErrorUrl); + const expectedXml = ` + + + Prebid org + Random Title + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should add Impression Nodes and Error Nodes to the Ad Wrapper and Inline', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(inLineWithWrapper, null, expectedImpressionUrl, expectedImpressionId, expectedErrorUrl); + const expectedXml = ` + + + Prebid org + + + + + + Prebid org + Random Title + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should override the ad id in inline', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(inlineXml, 'adIdOverride'); + const expectedXml = ` + + + Prebid org + Random Title + + +`; + expect(vastXml).to.equal(expectedXml); + }); + + it('should override the ad id in the Ad Wrapper', function () { + const vastXml = vastXmlEditor.getVastXmlWithTracking(adWrapperXml, 'adIdOverride'); + const expectedXml = ` + + + Prebid org + + + +`; + expect(vastXml).to.equal(expectedXml); + }); +}); diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js new file mode 100644 index 00000000000..e08353f5b8d --- /dev/null +++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js @@ -0,0 +1,881 @@ +import { + JWPlayerProvider, + adStateFactory, + timeStateFactory, + callbackStorageFactory, + utils +} from 'modules/jwplayerVideoProvider'; + +import { + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE +} from 'libraries/video/constants/ortb.js'; + +import { + SETUP_COMPLETE, SETUP_FAILED, PLAY, AD_IMPRESSION, videoEvents +} from 'libraries/video/constants/events.js'; + +import { PLAYBACK_MODE } from 'libraries/video/constants/constants.js'; + +function getPlayerMock() { + return makePlayerFactoryMock({ + getState: function () {}, + setup: function () {}, + getViewable: function () {}, + getPercentViewable: function () {}, + getMute: function () {}, + getVolume: function () {}, + getConfig: function () {}, + getHeight: function () {}, + getWidth: function () {}, + getFullscreen: function () {}, + getPlaylistItem: function () {}, + playAd: function () {}, + on: function () {}, + off: function () {}, + remove: function () {}, + getAudioTracks: function () {}, + getCurrentAudioTrack: function () {}, + getPlugin: function () {}, + getFloating: function () {} + })(); +} + +function makePlayerFactoryMock(playerMock_) { + const playerFactory = function () { + return playerMock_; + } + playerFactory.version = '8.21.0'; + return playerFactory; +} + +function getUtilsMock() { + return { + getJwConfig: function () {}, + getSupportedMediaTypes: function () {}, + getStartDelay: function () {}, + getPlacement: function () {}, + getPlaybackMethod: function () {}, + isOmidSupported: function () {}, + getSkipParams: function () {}, + getJwEvent: event => event, + getIsoLanguageCode: function () {}, + getSegments: function () {}, + getContentDatum: function () {} + }; +} + +const sharedUtils = { videoEvents }; + +function addDiv() { + const div = document.createElement('div'); + div.setAttribute('id', 'test'); + document.body.appendChild(div); +} + +function removeDiv() { + const div = document.getElementById('test'); + if (div) { + div.remove(); + } +} + +describe('JWPlayerProvider', function () { + beforeEach(() => { + addDiv(); + }); + + afterEach(() => { + removeDiv(); + }); + + describe('init', function () { + let config; + let adState; + let timeState; + let callbackStorage; + let utilsMock; + + beforeEach(() => { + config = { divId: 'test' }; + adState = adStateFactory(); + timeState = timeStateFactory(); + callbackStorage = callbackStorageFactory(); + utilsMock = getUtilsMock(); + }); + + it('should trigger failure when jwplayer is missing', function () { + const provider = JWPlayerProvider(config, null, adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-1); + }); + + it('should trigger failure when jwplayer version is under min supported version', function () { + let jwplayerMock = () => {}; + jwplayerMock.version = '8.20.0'; + const provider = JWPlayerProvider(config, jwplayerMock, adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-2); + }); + + it('should trigger failure when div is missing', function () { + removeDiv(); + let jwplayerMock = () => {}; + const provider = JWPlayerProvider(config, jwplayerMock, adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-3); + addDiv(); + addDiv(); + }); + + it('should instantiate the player when uninstantiated', function () { + const player = getPlayerMock(); + config.playerConfig = {}; + const setupSpy = player.setup = sinon.spy(); + const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); + provider.init(); + expect(setupSpy.calledOnce).to.be.true; + }); + + it('should trigger setup complete when player is already instantiated', function () { + const player = getPlayerMock(); + player.getState = () => 'idle'; + const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); + const setupComplete = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + }); + + it('should not reinstantiate player', function () { + const player = getPlayerMock(); + player.getState = () => 'idle'; + const setupSpy = player.setup = sinon.spy(); + const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adState, timeState, callbackStorage, utilsMock, sharedUtils); + provider.init(); + expect(setupSpy.called).to.be.false; + }); + }); + + describe('getId', function () { + it('should return configured div id', function () { + const provider = JWPlayerProvider({ divId: 'test_id' }, undefined, undefined, undefined, undefined, undefined, sharedUtils); + expect(provider.getId()).to.be.equal('test_id'); + }); + }); + + describe('getOrtbVideo', function () { + it('should populate oRTB Video params', function () { + const test_media_type = VIDEO_MIME_TYPE.MP4; + const test_height = 100; + const test_width = 200; + const test_start_delay = 5; + const test_placement = PLACEMENT.ARTICLE; + const test_battr = 'battr'; + const test_playback_method = PLAYBACK_METHODS.CLICK_TO_PLAY; + const test_skip = 0; + + const config = { divId: 'test' }; + const player = getPlayerMock(); + const utils = getUtilsMock(); + + player.getConfig = () => ({ + advertising: { + battr: test_battr + } + }); + player.getHeight = () => test_height; + player.getWidth = () => test_width; + player.getFullscreen = () => true; // + + utils.getSupportedMediaTypes = () => [test_media_type]; + utils.getStartDelay = () => test_start_delay; + utils.getPlacement = () => test_placement; + utils.getPlaybackMethod = () => test_playback_method; + utils.isOmidSupported = () => true; // + utils.getSkipParams = () => ({ skip: test_skip }); + + const provider = JWPlayerProvider(config, makePlayerFactoryMock(player), adStateFactory(), {}, {}, utils, sharedUtils); + provider.init(); + let video = provider.getOrtbVideo(); + + expect(video.mimes).to.include(VIDEO_MIME_TYPE.MP4); + expect(video.protocols).to.include.members([ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ]); + expect(video.h).to.equal(test_height); + expect(video.w).to.equal(test_width); + expect(video.startdelay).to.equal(test_start_delay); + expect(video.placement).to.equal(test_placement); + expect(video.battr).to.equal(test_battr); + expect(video.maxextended).to.equal(-1); + expect(video.boxingallowed).to.equal(1); + expect(video.playbackmethod).to.include(test_playback_method); + expect(video.playbackend).to.equal(1); + expect(video.api).to.have.length(2); + expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); // + expect(video.skip).to.equal(test_skip); + expect(video.pos).to.equal(7); // + + player.getFullscreen = () => false; + utils.isOmidSupported = () => false; + + video = provider.getOrtbVideo(); + expect(video).to.not.have.property('pos'); + expect(video.api).to.have.length(1); + expect(video.api).to.include(API_FRAMEWORKS.VPAID_2_0); + expect(video.api).to.not.include(API_FRAMEWORKS.OMID_1_0); + }); + }); + + describe('getOrtbContent', function () { + it('should populate oRTB Content params', function () { + const test_item = { + mediaid: 'id', + file: 'file', + title: 'title', + iabCategories: 'iabCategories', + tags: 'keywords', + }; + const test_duration = 30; + let test_playback_mode = PLAYBACK_MODE.VOD;// + + const player = getPlayerMock(); + player.getPlaylistItem = () => test_item; + const utils = getUtilsMock(); + + const timeState = { + getState: () => ({ + duration: test_duration, + playbackMode: test_playback_mode + }) + }; + + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeState, {}, utils, sharedUtils); + provider.init(); + + let content = provider.getOrtbContent(); + expect(content.id).to.be.equal('jw_' + test_item.mediaid); + expect(content.url).to.be.equal(test_item.file); + expect(content.title).to.be.equal(test_item.title); + expect(content.cat).to.be.equal(test_item.iabCategories); + expect(content.keywords).to.be.equal(test_item.tags); + expect(content.len).to.be.equal(test_duration); + expect(content.livestream).to.be.equal(0);// + + test_playback_mode = PLAYBACK_MODE.LIVE; + + content = provider.getOrtbContent(); + expect(content.livestream).to.be.equal(1); + + test_playback_mode = PLAYBACK_MODE.DVR; + + content = provider.getOrtbContent(); + expect(content.livestream).to.be.equal(1); + }); + }); + + describe('setAdTagUrl', function () { + it('should call playAd', function () { + const player = getPlayerMock(); + const playAdSpy = player.playAd = sinon.spy(); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), {}, {}, {}, {}, sharedUtils); + provider.init(); + provider.setAdTagUrl('tag'); + expect(playAdSpy.called).to.be.true; + const argument = playAdSpy.args[0][0]; + expect(argument).to.be.equal('tag'); + }); + }); + + describe('events', function () { + it('should register event listener on player', function () { + const player = getPlayerMock(); + const onSpy = player.on = sinon.spy(); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), getUtilsMock(), sharedUtils); + provider.init(); + const callback = () => {}; + provider.onEvent(PLAY, callback, {}); + expect(onSpy.calledOnce).to.be.true; + const eventName = onSpy.args[0][0]; + expect(eventName).to.be.equal('play'); + }); + + it('should remove event listener on player', function () { + const player = getPlayerMock(); + const offSpy = player.off = sinon.spy(); + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), utils, sharedUtils); + provider.init(); + const callback = () => {}; + provider.onEvent(AD_IMPRESSION, callback, {}); + provider.offEvent(AD_IMPRESSION, callback); + expect(offSpy.calledOnce).to.be.true; + const eventName = offSpy.args[0][0]; + expect(eventName).to.be.equal('adViewableImpression'); + }); + }); + + describe('destroy', function () { + it('should remove and null the player', function () { + const player = getPlayerMock(); + const removeSpy = player.remove = sinon.spy(); + player.remove = removeSpy; + const provider = JWPlayerProvider({ divId: 'test' }, makePlayerFactoryMock(player), adStateFactory(), timeStateFactory(), callbackStorageFactory(), getUtilsMock(), sharedUtils); + provider.init(); + provider.destroy(); + provider.destroy(); + expect(removeSpy.calledOnce).to.be.true; + }); + }); +}); + +describe('adStateFactory', function () { + let adState = adStateFactory(); + + beforeEach(() => { + adState.clearState(); + }); + + it('should update state for ad events', function () { + const tag = 'tag'; + const adPosition = 'adPosition'; + const timeLoading = 'timeLoading'; + const id = 'id'; + const description = 'description'; + const adsystem = 'adsystem'; + const adtitle = 'adtitle'; + const advertiserId = 'advertiserId'; + const advertiser = 'advertiser'; + const dealId = 'dealId'; + const linear = 'linear'; + const vastversion = 'vastversion'; + const mediaFile = 'mediaFile'; + const adId = 'adId'; + const universalAdId = 'universalAdId'; + const creativeAdId = 'creativeAdId'; + const creativetype = 'creativetype'; + const clickThroughUrl = 'clickThroughUrl'; + const witem = 'witem'; + const wcount = 'wcount'; + const podcount = 'podcount'; + const sequence = 'sequence'; + + adState.updateForEvent({ + tag, + adPosition, + timeLoading, + id, + description, + adsystem, + adtitle, + advertiserId, + advertiser, + dealId, + linear, + vastversion, + mediaFile, + adId, + universalAdId, + creativeAdId, + creativetype, + clickThroughUrl, + witem, + wcount, + podcount, + sequence + }); + + const state = adState.getState(); + expect(state.adTagUrl).to.equal(tag); + expect(state.offset).to.equal(adPosition); + expect(state.loadTime).to.equal(timeLoading); + expect(state.vastAdId).to.equal(id); + expect(state.adDescription).to.equal(description); + expect(state.adServer).to.equal(adsystem); + expect(state.adTitle).to.equal(adtitle); + expect(state.advertiserId).to.equal(advertiserId); + expect(state.dealId).to.equal(dealId); + expect(state.linear).to.equal(linear); + expect(state.vastVersion).to.equal(vastversion); + expect(state.creativeUrl).to.equal(mediaFile); + expect(state.adId).to.equal(adId); + expect(state.universalAdId).to.equal(universalAdId); + expect(state.creativeId).to.equal(creativeAdId); + expect(state.creativeType).to.equal(creativetype); + expect(state.redirectUrl).to.equal(clickThroughUrl); + expect(state).to.have.property('adPlacementType'); + expect(state.adPlacementType).to.be.undefined; + expect(state.waterfallIndex).to.equal(witem); + expect(state.waterfallCount).to.equal(wcount); + expect(state.adPodCount).to.equal(podcount); + expect(state.adPodIndex).to.equal(sequence); + }); + + it('should convert placement to oRTB value', function () { + adState.updateForEvent({ + placement: 'instream' + }); + + let state = adState.getState(); + expect(state.adPlacementType).to.be.equal(PLACEMENT.INSTREAM); + + adState.updateForEvent({ + placement: 'banner' + }); + + state = adState.getState(); + expect(state.adPlacementType).to.be.equal(PLACEMENT.BANNER); + + adState.updateForEvent({ + placement: 'article' + }); + + state = adState.getState(); + expect(state.adPlacementType).to.be.equal(PLACEMENT.ARTICLE); + + adState.updateForEvent({ + placement: 'feed' + }); + + state = adState.getState(); + expect(state.adPlacementType).to.be.equal(PLACEMENT.FEED); + + adState.updateForEvent({ + placement: 'interstitial' + }); + + state = adState.getState(); + expect(state.adPlacementType).to.be.equal(PLACEMENT.INTERSTITIAL); + + adState.updateForEvent({ + placement: 'slider' + }); + + state = adState.getState(); + expect(state.adPlacementType).to.be.equal(PLACEMENT.SLIDER); + + adState.updateForEvent({ + placement: 'floating' + }); + + state = adState.getState(); + expect(state.adPlacementType).to.be.equal(PLACEMENT.FLOATING); + }); +}); + +describe('timeStateFactory', function () { + let timeState = timeStateFactory(); + + beforeEach(() => { + timeState.clearState(); + }); + + it('should update state for VOD time event', function() { + const position = 5; + const test_duration = 30; + + timeState.updateForEvent({ + position, + duration: test_duration + }); + + const { time, duration, playbackMode } = timeState.getState(); + expect(time).to.be.equal(position); + expect(duration).to.be.equal(test_duration); + expect(playbackMode).to.be.equal(PLAYBACK_MODE.VOD); + }); + + it('should update state for LIVE time events', function() { + const position = 0; + const test_duration = 0; + + timeState.updateForEvent({ + position, + duration: test_duration + }); + + const { time, duration, playbackMode } = timeState.getState(); + expect(time).to.be.equal(position); + expect(duration).to.be.equal(test_duration); + expect(playbackMode).to.be.equal(PLAYBACK_MODE.LIVE); + }); + + it('should update state for DVR time events', function() { + const position = -5; + const test_duration = -30; + + timeState.updateForEvent({ + position, + duration: test_duration + }); + + const { time, duration, playbackMode } = timeState.getState(); + expect(time).to.be.equal(position); + expect(duration).to.be.equal(test_duration); + expect(playbackMode).to.be.equal(PLAYBACK_MODE.DVR); + }); +}); + +describe('callbackStorageFactory', function () { + let callbackStorage = callbackStorageFactory(); + + beforeEach(() => { + callbackStorage.clearStorage(); + }); + + it('should store callbacks', function () { + const callback1 = () => 'callback1'; + const eventHandler1 = () => 'eventHandler1'; + callbackStorage.storeCallback('event', eventHandler1, callback1); + + const callback2 = () => 'callback2'; + const eventHandler2 = () => 'eventHandler2'; + callbackStorage.storeCallback('event', eventHandler2, callback2); + + const callback3 = () => 'callback3'; + + expect(callbackStorage.getCallback('event', callback1)).to.be.equal(eventHandler1); + expect(callbackStorage.getCallback('event', callback2)).to.be.equal(eventHandler2); + expect(callbackStorage.getCallback('event', callback3)).to.be.undefined; + }); + + it('should remove callbacks after retrieval', function () { + const callback1 = () => 'callback1'; + const eventHandler1 = () => 'eventHandler1'; + callbackStorage.storeCallback('event', eventHandler1, callback1); + + expect(callbackStorage.getCallback('event', callback1)).to.be.equal(eventHandler1); + expect(callbackStorage.getCallback('event', callback1)).to.be.undefined; + }); + + it('should clear callbacks', function () { + const callback1 = () => 'callback1'; + const eventHandler1 = () => 'eventHandler1'; + callbackStorage.storeCallback('event', eventHandler1, callback1); + + callbackStorage.clearStorage(); + expect(callbackStorage.getCallback('event', callback1)).to.be.undefined; + }); +}); + +describe('utils', function () { + describe('getJwConfig', function () { + const getJwConfig = utils.getJwConfig; + it('should return undefined when no config is provided', function () { + let jwConfig = getJwConfig(); + expect(jwConfig).to.be.undefined; + + jwConfig = getJwConfig(null); + expect(jwConfig).to.be.undefined; + }); + + it('should set vendor config params to top level', function () { + let jwConfig = getJwConfig({ + params: { + vendorConfig: { + 'test': 'a', + 'test_2': 'b' + } + } + }); + expect(jwConfig.test).to.be.equal('a'); + expect(jwConfig.test_2).to.be.equal('b'); + }); + + it('should convert video module params', function () { + let jwConfig = getJwConfig({ + mute: true, + autoStart: true, + licenseKey: 'key' + }); + + expect(jwConfig.mute).to.be.true; + expect(jwConfig.autostart).to.be.true; + expect(jwConfig.key).to.be.equal('key'); + }); + + it('should apply video module params only when absent from vendor config', function () { + let jwConfig = getJwConfig({ + mute: true, + autoStart: true, + licenseKey: 'key', + params: { + vendorConfig: { + mute: false, + autostart: false, + key: 'other_key' + } + } + }); + + expect(jwConfig.mute).to.be.false; + expect(jwConfig.autostart).to.be.false; + expect(jwConfig.key).to.be.equal('other_key'); + }); + + it('should not convert undefined properties', function () { + let jwConfig = getJwConfig({ + params: { + vendorConfig: { + test: 'a' + } + } + }); + + expect(jwConfig).to.not.have.property('mute'); + expect(jwConfig).to.not.have.property('autostart'); + expect(jwConfig).to.not.have.property('key'); + }); + + it('should exclude fallback ad block when setupAds is explicitly disabled', function () { + let jwConfig = getJwConfig({ + setupAds: false, + params: { + + vendorConfig: {} + } + }); + + expect(jwConfig).to.not.have.property('advertising'); + }); + + it('should set advertising block when setupAds is allowed', function () { + let jwConfig = getJwConfig({ + params: { + vendorConfig: { + advertising: { + tag: 'test_tag' + } + } + } + }); + + expect(jwConfig).to.have.property('advertising'); + expect(jwConfig.advertising).to.have.property('tag', 'test_tag'); + }); + + it('should fallback to vast plugin', function () { + let jwConfig = getJwConfig({}); + + expect(jwConfig).to.have.property('advertising'); + expect(jwConfig.advertising).to.have.property('client', 'vast'); + }); + }); + describe('getSkipParams', function () { + const getSkipParams = utils.getSkipParams; + + it('should return an empty object when skip is not configured', function () { + let skipParams = getSkipParams({}); + expect(skipParams).to.be.empty; + }); + + it('should set skip to false when explicitly configured', function () { + let skipParams = getSkipParams({ + skipoffset: -1 + }); + expect(skipParams.skip).to.be.equal(0); + expect(skipParams.skipmin).to.be.undefined; + expect(skipParams.skipafter).to.be.undefined; + }); + + it('should be skippable when skip offset is set', function () { + const skipOffset = 3; + let skipParams = getSkipParams({ + skipoffset: skipOffset + }); + expect(skipParams.skip).to.be.equal(1); + expect(skipParams.skipmin).to.be.equal(skipOffset + 2); + expect(skipParams.skipafter).to.be.equal(skipOffset); + }); + }); + + describe('getSupportedMediaTypes', function () { + const getSupportedMediaTypes = utils.getSupportedMediaTypes; + + it('should always support VPAID', function () { + let supportedMediaTypes = getSupportedMediaTypes([]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + + supportedMediaTypes = getSupportedMediaTypes([VIDEO_MIME_TYPE.MP4]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + }); + }); + + describe('getPlacement', function () { + const getPlacement = utils.getPlacement; + + it('should be INSTREAM when not configured for outstream', function () { + let adConfig = {}; + let placement = getPlacement(adConfig); + expect(placement).to.be.equal(PLACEMENT.INSTREAM); + + adConfig = { outstream: false }; + placement = getPlacement(adConfig); + expect(placement).to.be.equal(PLACEMENT.INSTREAM); + }); + + it('should be FLOATING when player is floating', function () { + const player = getPlayerMock(); + player.getFloating = () => true; + const placement = getPlacement({outstream: true}, player); + expect(placement).to.be.equal(PLACEMENT.FLOATING); + }); + + it('should be the value defined in the ad config', function () { + const player = getPlayerMock(); + player.getFloating = () => false; + + let placement = getPlacement({placement: 'banner', outstream: true}, player); + expect(placement).to.be.equal(PLACEMENT.BANNER); + + placement = getPlacement({placement: 'article', outstream: true}, player); + expect(placement).to.be.equal(PLACEMENT.ARTICLE); + + placement = getPlacement({placement: 'feed', outstream: true}, player); + expect(placement).to.be.equal(PLACEMENT.FEED); + + placement = getPlacement({placement: 'interstitial', outstream: true}, player); + expect(placement).to.be.equal(PLACEMENT.INTERSTITIAL); + + placement = getPlacement({placement: 'slider', outstream: true}, player); + expect(placement).to.be.equal(PLACEMENT.SLIDER); + }); + + it('should be undefined when undetermined', function () { + const placement = getPlacement({ outstream: true }, getPlayerMock()); + expect(placement).to.be.undefined; + }); + }); + + describe('getPlaybackMethod', function() { + const getPlaybackMethod = utils.getPlaybackMethod; + + it('should return autoplay with sound', function() { + const playbackMethod = getPlaybackMethod({ + autoplay: true, + mute: false + }); + expect(playbackMethod).to.equal(PLAYBACK_METHODS.AUTOPLAY); + }); + + it('should return autoplay muted', function() { + const playbackMethod = getPlaybackMethod({ + autoplay: true, + mute: true + }); + expect(playbackMethod).to.equal(PLAYBACK_METHODS.AUTOPLAY_MUTED); + }); + + it('should treat autoplayAdsMuted as mute', function () { + const playbackMethod = getPlaybackMethod({ + autoplay: true, + autoplayAdsMuted: true + }); + expect(playbackMethod).to.equal(PLAYBACK_METHODS.AUTOPLAY_MUTED); + }); + + it('should return click to play', function() { + let playbackMethod = getPlaybackMethod({ autoplay: false }); + expect(playbackMethod).to.equal(PLAYBACK_METHODS.CLICK_TO_PLAY); + + playbackMethod = getPlaybackMethod({ + autoplay: false, + autoplayAdsMuted: true + }); + expect(playbackMethod).to.equal(PLAYBACK_METHODS.CLICK_TO_PLAY); + + playbackMethod = getPlaybackMethod({ + autoplay: false, + mute: true + }); + expect(playbackMethod).to.equal(PLAYBACK_METHODS.CLICK_TO_PLAY); + }); + }); + + describe('isOmidSupported', function () { + const isOmidSupported = utils.isOmidSupported; + const initialOmidSessionClient = window.OmidSessionClient; + afterEach(() => { + window.OmidSessionClient = initialOmidSessionClient; + }); + + it('should be true when Omid is loaded and client is VAST', function () { + window.OmidSessionClient = {}; + expect(isOmidSupported('vast')).to.be.true; + }); + + it('should be false when Omid is not present', function () { + expect(isOmidSupported('vast')).to.be.false; + }); + + it('should be false when client is not Vast', function () { + window.OmidSessionClient = {}; + expect(isOmidSupported('googima')).to.be.false; + expect(isOmidSupported('freewheel')).to.be.false; + expect(isOmidSupported('googimadai')).to.be.false; + expect(isOmidSupported('')).to.be.false; + expect(isOmidSupported(null)).to.be.false; + expect(isOmidSupported()).to.be.false; + }); + }); + + describe('getIsoLanguageCode', function () { + const sampleAudioTracks = [{language: 'ht'}, {language: 'fr'}, {language: 'es'}, {language: 'pt'}]; + + it('should return undefined when audio tracks are unavailable', function () { + const player = getPlayerMock(); + let languageCode = utils.getIsoLanguageCode(player); + expect(languageCode).to.be.undefined; + player.getAudioTracks = () => []; + languageCode = utils.getIsoLanguageCode(player); + expect(languageCode).to.be.undefined; + }); + + it('should return the first audio track language code if the getCurrentAudioTrack returns undefined', function () { + const player = getPlayerMock(); + player.getAudioTracks = () => sampleAudioTracks; + let languageCode = utils.getIsoLanguageCode(player); + expect(languageCode).to.be.equal('ht'); + }); + + it('should return the first audio track language code if the getCurrentAudioTrack returns null', function () { + const player = getPlayerMock(); + player.getAudioTracks = () => sampleAudioTracks; + player.getCurrentAudioTrack = () => null; + let languageCode = utils.getIsoLanguageCode(player); + expect(languageCode).to.be.equal('ht'); + }); + + it('should return the first audio track language code if the getCurrentAudioTrack returns -1', function () { + const player = getPlayerMock(); + player.getAudioTracks = () => sampleAudioTracks; + player.getCurrentAudioTrack = () => -1; + const languageCode = utils.getIsoLanguageCode(player); + expect(languageCode).to.be.equal('ht'); + }); + + it('should return the right audio track language code', function () { + const player = getPlayerMock(); + player.getAudioTracks = () => sampleAudioTracks; + player.getCurrentAudioTrack = () => 2; + const languageCode = utils.getIsoLanguageCode(player); + expect(languageCode).to.be.equal('es'); + }); + }); +}); diff --git a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js new file mode 100644 index 00000000000..a7379ccbab2 --- /dev/null +++ b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js @@ -0,0 +1,391 @@ +// Using require style imports for fine grained control of import time +import { + SETUP_COMPLETE, SETUP_FAILED +} from 'libraries/video/constants/events.js'; + +const {VideojsProvider, utils} = require('modules/videojsVideoProvider'); + +const { + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION +} = require('libraries/video/constants/ortb.js'); + +const videojs = require('video.js').default; +require('videojs-playlist').default; +require('videojs-ima').default; +require('videojs-contrib-ads').default; + +describe('videojsProvider', function () { + let config; + let adState; + let timeState; + let callbackStorage; + + describe('init', function () { + beforeEach(() => { + config = {}; + document.body.innerHTML = ''; + }); + + it('should trigger failure when videojs is missing', function () { + const provider = VideojsProvider(config, null, adState, timeState, callbackStorage, utils); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-1); + }); + + it('should trigger failure when videojs version is under min supported version', function () { + const provider = VideojsProvider(config, {...videojs, VERSION: '0.0.0'}, adState, timeState, callbackStorage, utils); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-2); + }); + + it('should trigger failure when the div is not found', function () { + config.divId = 'fake-div' + const provider = VideojsProvider(config, videojs, adState, timeState, callbackStorage, utils); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-3); + }); + + it('should instantiate the player when uninstantied', function () { + config.playerConfig = {testAttr: true}; + config.divId = 'test-div' + const div = document.createElement('div'); + div.setAttribute('id', 'test-div'); + document.body.appendChild(div); + + const mockVideojs = sinon.spy(); + const provider = VideojsProvider(config, mockVideojs, adState, timeState, callbackStorage, utils); + provider.init(); + expect(mockVideojs.calledOnce).to.be.true + }); + + it('should not reinstantiate the player', function () { + const div = document.createElement('div'); + div.setAttribute('id', 'test-div'); + document.body.appendChild(div); + const player = videojs(div, {}) + config.playerConfig = {}; + config.divId = 'test-div' + const provider = VideojsProvider(config, videojs, adState, timeState, callbackStorage, utils); + provider.init(); + expect(videojs.getPlayer('test-div')).to.be.equal(player) + videojs.getPlayer('test-div').dispose() + }); + + it('should trigger setup complete when player is already insantiated', function () { + const div = document.createElement('div'); + div.setAttribute('id', 'test-div'); + document.body.appendChild(div); + videojs(div, {}) + config.playerConfig = {}; + config.divId = 'test-div' + const provider = VideojsProvider(config, videojs, adState, timeState, callbackStorage, utils); + const setupComplete = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.init(); + expect(setupComplete.called).to.be.true; + videojs.getPlayer('test-div').dispose() + }); + }); + + describe('getId', function () { + it('should return configured div id', function () { + const provider = VideojsProvider({ divId: 'test_id' }); + expect(provider.getId()).to.be.equal('test_id'); + }); + }); + + describe('getOrtbParams', function () { + beforeEach(() => { + config = {divId: 'test'}; + // initialize videojs element + document.body.innerHTML = ` + `; + }); + + afterEach(() => { + const testPlayer = videojs('test'); + if (testPlayer && !testPlayer.isDisposed()) { + testPlayer.dispose(); + } + }); + + it('should populate oRTB Video and Content', function () { + const provider = VideojsProvider(config, videojs, adState, timeState, callbackStorage, utils); + provider.init(); + + const video = provider.getOrtbVideo(); + + expect(video.mimes).to.include(VIDEO_MIME_TYPE.MP4); + expect(video.protocols).to.deep.equal([2]); + expect(video.h).to.equal(100); + expect(video.w).to.equal(200); + + expect(video.maxextended).to.equal(-1); + expect(video.boxingallowed).to.equal(1); + expect(video.playbackmethod).to.include(PLAYBACK_METHODS.CLICK_TO_PLAY); + expect(video.playbackend).to.equal(1); + expect(video.api).to.deep.equal([2]); + expect(video.placement).to.be.equal(PLACEMENT.INSTREAM); + }); + + it('should populate oRTB Content', function () { + const provider = VideojsProvider(config, videojs, adState, timeState, callbackStorage, utils); + provider.init(); + + const content = provider.getOrtbContent(); + expect(content.url).to.be.equal('http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); + expect(content).to.not.have.property('len'); + }); + + it('should change populated oRTB params when ima present', function () { + require('videojs-contrib-ads'); + require('videojs-ima'); + config.playerConfig = { + params: { + vendorConfig: { + mediaid: 'vendor-id', + advertising: { + tag: ['test-tag'] + } + } + } + } + + let provider = VideojsProvider(config, videojs, null, null, null, utils); + provider.init(); + const video = provider.getOrtbVideo(); + + expect(video.protocols).to.include(PROTOCOLS.VAST_2_0); + expect(video.api).to.include(API_FRAMEWORKS.VPAID_2_0); + expect(video.mimes).to.include(VPAID_MIME_TYPE); + }); + // + // We can't determine what type of outstream play is occuring + // if the src is absent so we should not set placement + it('should not set placement when src is absent', function() { + document.body.innerHTML = `` + const provider = VideojsProvider(config, videojs, null, null, null, utils); + provider.init(); + const video = provider.getOrtbVideo(); + expect(video).to.not.have.property('placement') + }) + // + it('should populate position when fullscreen', function () { + const provider = VideojsProvider(config, videojs, null, null, null, utils); + provider.init(); + const player = videojs.getPlayer('test') + player.isFullscreen = () => true; + const video = provider.getOrtbVideo(); + expect(video.pos).to.equal(7); + }); + // + it('should populate length when loaded', function () { + const provider = VideojsProvider(config, videojs, null, null, null, utils); + provider.init(); + const player = videojs.getPlayer('test') + player.readyState = () => 1 + player.duration = () => 100 + const content = provider.getOrtbContent(); + expect(content.len).to.equal(100); + }); + // + it('should return the correct playback method for autoplay', function () { + const provider = VideojsProvider(config, videojs, null, null, null, utils); + provider.init(); + const player = videojs.getPlayer('test') + player.autoplay(true) + const video = provider.getOrtbVideo(); + expect(video.playbackmethod).to.include(PLAYBACK_METHODS.AUTOPLAY); + }); + // + it('should return the correct playback method for autoplay muted', function () { + const provider = VideojsProvider(config, videojs, null, null, null, utils); + provider.init(); + const player = videojs.getPlayer('test') + player.muted = () => true + player.autoplay = () => true + const video = provider.getOrtbVideo(); + expect(video.playbackmethod).to.include(PLAYBACK_METHODS.AUTOPLAY_MUTED); + }); + // + it('should return the correct playback method for the other autoplay muted', function () { + const provider = VideojsProvider(config, videojs, null, null, null, utils); + provider.init(); + const player = videojs.getPlayer('test') + player.autoplay = () => 'muted' + const video = provider.getOrtbVideo(); + expect(video.playbackmethod).to.include(PLAYBACK_METHODS.AUTOPLAY_MUTED); + }); + }); +}); + +describe('utils', function() { + describe('getSetupConfig', function() { + it('should return undefined when config is absent', function () { + expect(utils.getSetupConfig()).to.be.undefined; + }); + + it('should give priority to vendorConfig', function () { + const config = { + autostart: false, + mute: false, + params: { + vendorConfig: { + autostart: true, + mute: true, + other: true + } + } + }; + const setupConfig = utils.getSetupConfig(config); + expect(setupConfig.autostart).to.be.true; + expect(setupConfig.mute).to.be.true; + expect(setupConfig.other).to.be.true; + }); + + it('should only global apply properties when absent from vendor config', function () { + const config = { + autostart: false, + params: { + vendorConfig: { + other: true + } + } + }; + const setupConfig = utils.getSetupConfig(config); + expect(setupConfig.autostart).to.be.false; + expect(setupConfig.mute).to.be.undefined; + expect(setupConfig.other).to.be.true; + }); + }); + + describe('getAdConfig', function () { + it('should return empty object when config is absent', function () { + expect(utils.getAdConfig()).to.deep.equal({}); + }); + + it('should return adPluginConfig', function () { + const config = { + params: { + adPluginConfig: { + vpaid: true, + } + } + }; + + expect(utils.getAdConfig(config)).to.be.equal(config.params.adPluginConfig); + }); + }); + + describe('getPositionCode', function() { + it('should return the correct position when video is above the fold', function () { + const code = utils.getPositionCode({ + left: window.innerWidth / 10, + top: 0, + width: window.innerWidth - window.innerWidth / 10, + height: window.innerHeight, + }) + expect(code).to.equal(AD_POSITION.ABOVE_THE_FOLD) + }); + + it('should return the correct position when video is below the fold', function () { + const code = utils.getPositionCode({ + left: window.innerWidth / 10, + top: window.innerHeight, + width: window.innerWidth - window.innerWidth / 10, + height: window.innerHeight / 2, + }) + expect(code).to.equal(AD_POSITION.BELOW_THE_FOLD) + }); + + it('should return the unkown position when the video is out of bounds', function () { + const code = utils.getPositionCode({ + left: window.innerWidth / 10, + top: window.innerHeight, + width: window.innerWidth, + height: window.innerHeight, + }) + expect(code).to.equal(AD_POSITION.UNKNOWN) + }); + }); + + describe('Playlist', function () { + const emptyPlayer = {}; + + describe('getPlaylistCount', function () { + it('should return 1 when playlist is absent', function () { + expect(utils.getPlaylistCount(emptyPlayer)).to.be.equal(1); + }); + + it('should return playlist length', function () { + document.body.innerHTML = ` + `; + const player = videojs('test', {}); + player.playlist([{ + sources: { src: 'sample.mp4' } + }, { + sources: { src: 'sample2.mp4' } + }]); + expect(utils.getPlaylistCount(player)).to.be.equal(2); + player.dispose(); + }); + }); + + describe('getCurrentPlaylistIndex', function () { + it('should return 0 when playlist is absent', function () { + expect(utils.getCurrentPlaylistIndex(emptyPlayer)).to.be.equal(0); + }); + }); + + describe('getCurrentPlaylistItem', function () { + it('should return undefined when playlist is absent', function () { + expect(utils.getCurrentPlaylistItem(emptyPlayer)).to.be.undefined; + }); + }); + }); + + describe('Get Media', function () { + describe('parseSource', function () { + it('should return src property when source is object', function () { + expect(utils.parseSource({ + src: 'test.url', + other: 'other' + })).to.be.equal('test.url'); + }); + + it('should return source when it is a string', function () { + expect(utils.parseSource('test.url')).to.be.equal('test.url'); + }); + + it('should return undefined when not object or string', function () { + expect(utils.parseSource(() => {})).to.be.undefined; + }); + }); + + describe('getMediaUrl', function () { + it('should return undefined when arg is missing', function () { + expect(utils.getMediaUrl()).to.be.undefined; + }); + + it('should parse first index when arg is array', function () { + expect(utils.getMediaUrl(['test.url.1', 'test.url.2'])).to.be.equal('test.url.1'); + }); + }); + }); +}) diff --git a/test/spec/modules/videoModule/videoImpressionVerifier_spec.js b/test/spec/modules/videoModule/videoImpressionVerifier_spec.js new file mode 100644 index 00000000000..58109219a37 --- /dev/null +++ b/test/spec/modules/videoModule/videoImpressionVerifier_spec.js @@ -0,0 +1,112 @@ +import { baseImpressionVerifier, PB_PREFIX } from 'modules/videoModule/videoImpressionVerifier.js'; + +let trackerMock; +trackerMock = { + store: sinon.spy(), + remove: sinon.spy() +} + +describe('Base Impression Verifier', function() { + describe('trackBid', function () { + it('should generate uuid', function () { + const baseVerifier = baseImpressionVerifier(trackerMock); + const uuid = baseVerifier.trackBid({}); + expect(uuid.substring(0, 3)).to.equal(PB_PREFIX); + expect(uuid.length).to.be.lessThan(16); + }); + }); + + describe('getBidIdentifiers', function () { + it('should match ad id to uuid', function () { + + }); + }); +}); + +/* +const adUnitCode = 'test_ad_unit_code'; +const sampleBid = { + adId: 'test_ad_id', + adUnitCode, + vastUrl: 'test_ad_url' +}; +const sampleAdUnit = { + code: adUnitCode, +}; + +const expectedImpressionUrl = 'test_impression_url'; +const expectedImpressionId = 'test_impression_id'; +const expectedErrorUrl = 'test_error_url'; +const expectedVastXml = 'test_xml'; + +it('should not modify the bid\'s adXml when the tracking config is omitted', function () { + const adUnit = Object.assign({}, sampleAdUnit, { video: { adServer: { tracking: null } } }); + const pbGlobal = Object.assign({}, pbGlobalMock, { adUnits: [ adUnit ] }); + pbVideoFactory(null, () => ({}), pbGlobal, pbEvents); + + bidAdjustmentCb(sampleBid); + // expect(vastXmlEditorMock.getVastXmlWithTrackingNodes.called).to.be.false; + // expect(vastXmlEditorMock.buildVastWrapper.called).to.be.false; +}); + +it('should request a vast wrapper when only an ad url is provided', function () { + const adUnit = Object.assign({}, sampleAdUnit, { video: { adServer: { tracking: { } } } }); + const pbGlobal = Object.assign({}, pbGlobalMock, { adUnits: [ adUnit ] }); + pbVideoFactory(null, () => ({}), pbGlobal, pbEvents); + + bidAdjustmentCb(sampleBid); + // expect(vastXmlEditorMock.getVastXmlWithTrackingNodes.called).to.be.false; + // expect(vastXmlEditorMock.buildVastWrapper.called).to.be.true; +}); + +it('should request the addition of tracking nodes when an ad xml is provided', function () { + const adUnit = Object.assign({}, sampleAdUnit, { video: { adServer: { tracking: { } } } }); + const pbGlobal = Object.assign({}, pbGlobalMock, { adUnits: [ adUnit ] }); + pbVideoFactory(null, () => ({}), pbGlobal, pbEvents); + + const bid = Object.assign({}, sampleBid, { vastXml: 'test_xml' }); + bidAdjustmentCb(bid); + // expect(vastXmlEditorMock.getVastXmlWithTrackingNodes.called).to.be.true; + // expect(vastXmlEditorMock.buildVastWrapper.called).to.be.false; +}); + +it('should pass the tracking information as args to the xml editing function', function () { + const adUnit = Object.assign({}, sampleAdUnit, { video: { adServer: { tracking: { + impression: { + url: expectedImpressionUrl, + id: expectedImpressionId + }, + error: { + url: expectedErrorUrl + } + } } } }); + const pbGlobal = Object.assign({}, pbGlobalMock, { adUnits: [ adUnit ] }); + pbVideoFactory(null, () => ({}), pbGlobal, pbEvents); + + const bid = Object.assign({}, sampleBid, { vastXml: expectedVastXml }); + bidAdjustmentCb(bid); + // expect(vastXmlEditorMock.getVastXmlWithTrackingNodes.called).to.be.true; + // expect(vastXmlEditorMock.getVastXmlWithTrackingNodes.calledWith(expectedVastXml, expectedImpressionUrl, expectedImpressionId, expectedErrorUrl)) + // expect(vastXmlEditorMock.buildVastWrapper.called).to.be.false; +}); + +it('should generate the impression id when not specified in config', function () { + const adUnit = Object.assign({}, sampleAdUnit, { video: { adServer: { tracking: { + impression: { + url: expectedImpressionUrl, + }, + error: { + url: expectedErrorUrl + } + } } } }); + const pbGlobal = Object.assign({}, pbGlobalMock, { adUnits: [ adUnit ] }); + pbVideoFactory(null, () => ({}), pbGlobal, pbEvents); + + const bid = Object.assign({}, sampleBid, { vastXml: expectedVastXml }); + bidAdjustmentCb(bid); + const expectedGeneratedId = sampleBid.adId + '-impression'; + // expect(vastXmlEditorMock.getVastXmlWithTrackingNodes.called).to.be.true; + // expect(vastXmlEditorMock.getVastXmlWithTrackingNodes.calledWith(expectedVastXml, expectedImpressionUrl, expectedGeneratedId, expectedErrorUrl)) + // expect(vastXmlEditorMock.buildVastWrapper.called).to.be.false; +}); +*/ diff --git a/test/spec/modules/viqeoBidAdapter_spec.js b/test/spec/modules/viqeoBidAdapter_spec.js new file mode 100644 index 00000000000..8f597318af9 --- /dev/null +++ b/test/spec/modules/viqeoBidAdapter_spec.js @@ -0,0 +1,124 @@ +import {expect} from 'chai'; +import {spec} from 'modules/viqeoBidAdapter'; + +describe('viqeoBidAdapter', function () { + it('minimal params', function () { + expect(spec.isBidRequestValid({ + bidder: 'viqeo', + params: { + user: { + buyeruid: '1', + }, + playerOptions: { + videoId: 'ed584da454c7205ca7e4', + profileId: 1382, + }, + }})).to.equal(true); + }); + it('minimal params no playerOptions', function () { + expect(spec.isBidRequestValid({ + bidder: 'viqeo', + params: { + currency: 'EUR', + }})).to.equal(false); + }); + it('build request check data', function () { + const bidRequestData = [{ + bidId: 'id1', + bidder: 'viqeo', + params: { + user: { + buyeruid: '1', + }, + currency: 'EUR', + floor: 0.5, + playerOptions: { + videoId: 'ed584da454c7205ca7e4', + profileId: 1382, + }, + }, + mediaTypes: { + video: { playerSize: [[240, 400]] } + }, + }]; + const request = spec.buildRequests(bidRequestData); + const requestData = request[0].data; + expect(requestData.id).to.equal('id1') + expect(requestData.imp[0].bidfloorcur).to.equal('EUR'); + expect(requestData.imp[0].bidfloor).to.equal(0.5); + expect(requestData.imp[0].video.w).to.equal(240); + expect(requestData.imp[0].video.h).to.equal(400); + expect(requestData.user.buyeruid).to.equal('1'); + }); + it('build request check url', function () { + const bidRequestData = [{ + bidder: 'viqeo', + params: { + playerOptions: { + videoId: 'ed584da454c7205ca7e4', + profileId: 1382, + }, + sspId: 42, + }, + mediaTypes: { + video: { playerSize: [[240, 400]] } + }, + }]; + const request = spec.buildRequests(bidRequestData); + expect(request[0].url).to.equal('https://ads.betweendigital.com/openrtb_bid/?sspId=42') + }); + it('response_params common case', function () { + const bidRequestData = { + bids: [{ + bidId: 'id1', + params: {}, + mediaTypes: { + video: { playerSize: [[240, 400]] } + }, + }], + }; + const serverResponse = { + body: { + id: 'id1', + cur: 'EUR', + seatbid: [{ + bid: [{ + cpm: 0.5, + ttl: 3600, + netRevenue: true, + creativeId: 'test1', + adm: '', + }], + }], + } + }; + const bids = spec.interpretResponse(serverResponse, bidRequestData); + expect(bids).to.have.lengthOf(1); + }); + it('should set flooPrice to getFloor.floor value if it is greater than params.floor', function() { + const bidRequestData = [{ + bidId: 'id1', + bidder: 'viqeo', + params: { + currency: 'EUR', + floor: 0.5, + playerOptions: { + videoId: 'ed584da454c7205ca7e4', + profileId: 1382, + }, + }, + mediaTypes: { + video: { playerSize: [[240, 400]] } + }, + getFloor: () => { + return { + currency: 'EUR', + floor: 3.32 + } + }, + }]; + const request = spec.buildRequests(bidRequestData); + const requestData = request[0].data; + expect(requestData.imp[0].bidfloor).to.equal(3.32) + }); +}); diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index 2f9f1b96142..d911745d378 100644 --- a/test/spec/modules/vrtcalBidAdapter_spec.js +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai' import { spec } from 'modules/vrtcalBidAdapter' import { newBidder } from 'src/adapters/bidderFactory' import { config } from 'src/config.js'; +import { createEidsArray } from 'modules/userId/eids.js'; describe('vrtcalBidAdapter', function () { const adapter = newBidder(spec) @@ -27,6 +28,7 @@ describe('vrtcalBidAdapter', function () { 'bidId': 'bidID0001', 'bidderRequestId': 'br0001', 'auctionId': 'auction0001', + 'userIdAsEids': {} } ]; @@ -58,7 +60,7 @@ describe('vrtcalBidAdapter', function () { config.setConfig({ coppa: false }); request = spec.buildRequests(bidRequests); - expect(request[0].data).to.match(/"user":{"ext":{"consent":"gdpr-consent-string"}}/); + expect(request[0].data).to.match(/"user":{"ext":{"consent":"gdpr-consent-string"/); expect(request[0].data).to.match(/"regs":{"coppa":0,"ext":{"gdpr":1,"us_privacy":"ccpa-consent-string"}}/); }); @@ -67,6 +69,15 @@ describe('vrtcalBidAdapter', function () { request = spec.buildRequests(bidRequests); expect(request[0].data).to.match(/"tmax":435/); }); + + it('pass 3rd party IDs with the request when present', function () { + bidRequests[0].userIdAsEids = createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE' + }); + + request = spec.buildRequests(bidRequests); + expect(request[0].data).to.include(JSON.stringify({ext: {consent: 'gdpr-consent-string', eids: [{source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}]}})); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index 523406dc492..bbc1c6d6f02 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -153,6 +153,289 @@ describe('weboramaRtdProvider', function() { }); }); + it('should use asset id when available and set gam targeting and send to bidders by default', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + assetID: 'datasource:docId', + targetURL: 'https://prebid.org', + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + webo_vctx: ['foo', 'bar'], + }; + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/document-profile?token=foo&assetId=datasource%3AdocId&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_vctx=foo;webo_vctx=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_vctx=foo,bar'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: data + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'contextual', + isDefault: false, + }, + }); + }); + + it('should use asset id as callback when available and set gam targeting and send to bidders by default', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + assetID: () => 'datasource:docId', + targetURL: 'https://prebid.org', + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const data = { + webo_vctx: ['foo', 'bar'], + }; + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/document-profile?token=foo&assetId=datasource%3AdocId&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_vctx=foo;webo_vctx=bar'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('webo_vctx=foo,bar'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: data + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ + site: { + ext: { + data: data + }, + } + }); + expect(onDataResponse).to.deep.equal({ + data: data, + meta: { + user: false, + source: 'contextual', + isDefault: false, + }, + }); + }); + + it('should handle exception from asset id callback', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + assetID: () => { + throw new Error('ops'); + }, + targetURL: 'https://prebid.org', + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(server.requests.length).to.equal(0); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({}); + }); + + it('should handle case when callback return falsy value', function() { + let onDataResponse = {}; + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + assetID: () => '', + targetURL: 'https://prebid.org', + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(server.requests.length).to.equal(0); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({}); + }); + describe('should set gam targeting and send to one specific bidder and multiple adunits', function() { const testcases = { 'single string': 'appnexus', @@ -1090,6 +1373,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -1199,6 +1483,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -1311,6 +1596,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -1418,6 +1704,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -1560,6 +1847,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -1700,6 +1988,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -1760,6 +2049,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -1859,6 +2149,7 @@ describe('weboramaRtdProvider', function() { } }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); const adUnitCode = 'adunit1'; @@ -1932,6 +2223,7 @@ describe('weboramaRtdProvider', function() { } }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(false); const adUnitCode = 'adunit1'; @@ -2029,6 +2321,7 @@ describe('weboramaRtdProvider', function() { targeting: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) @@ -2146,6 +2439,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) @@ -2254,6 +2548,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) @@ -2365,6 +2660,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) @@ -2471,6 +2767,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) @@ -2612,6 +2909,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) @@ -2751,6 +3049,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) @@ -2814,6 +3113,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) @@ -2912,6 +3212,7 @@ describe('weboramaRtdProvider', function() { } }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); const adUnitCode = 'adunit1'; @@ -2984,6 +3285,7 @@ describe('weboramaRtdProvider', function() { } }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(false); const adUnitCode = 'adunit1'; @@ -3044,6 +3346,85 @@ describe('weboramaRtdProvider', function() { }); }); + it('should use default profile if has no local storage', function() { + const defaultProfile = { + lite_hobbies: ['sport', 'cinéma'], + }; + let onDataResponse = {}; + const moduleConfig = { + params: { + sfbxLiteDataConf: { + setPrebidTargeting: true, + defaultProfile: defaultProfile, + onData: (data, meta) => { + onDataResponse = { + data: data, + meta: meta, + }; + }, + } + } + }; + + sandbox.stub(storage, 'hasLocalStorage').returns(false); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('lite_hobbies=sport;lite_hobbies=cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[1].params.dctr).to.equal('lite_hobbies=sport,cinéma'); + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ + inventory: defaultProfile, + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ + site: { + ext: { + data: defaultProfile, + }, + }, + }); + expect(onDataResponse).to.deep.equal({ + data: defaultProfile, + meta: { + user: false, + source: 'lite', + isDefault: true, + }, + }); + }); it('should be possible update profile from callbacks for a given bidder/adUnitCode', function() { let onDataResponse = {}; const moduleConfig = { @@ -3080,6 +3461,7 @@ describe('weboramaRtdProvider', function() { webo: data, }; + sandbox.stub(storage, 'hasLocalStorage').returns(true); sandbox.stub(storage, 'localStorageIsEnabled').returns(true); sandbox.stub(storage, 'getDataFromLocalStorage') .withArgs(DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY) diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index e0b6390832b..a326b22ecb4 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -2,6 +2,8 @@ import { expect } from 'chai'; import { config } from 'src/config.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { spec } from 'modules/yahoosspBidAdapter.js'; +import {createEidsArray} from '../../../modules/userId/eids'; +import {deepClone} from '../../../src/utils'; const DEFAULT_BID_ID = '84ab500420319d'; const DEFAULT_BID_DCN = '2093845709823475'; @@ -712,7 +714,7 @@ describe('YahooSSP Bid Adapter:', () => { }); }); - describe('GDPR & Consent:', () => { + describe('GDPR & Consent & GPP:', () => { it('should return request objects that do not send cookies if purpose 1 consent is not provided', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); bidderRequest.gdprConsent = { @@ -730,6 +732,20 @@ describe('YahooSSP Bid Adapter:', () => { const options = spec.buildRequests(validBidRequests, bidderRequest)[0].options; expect(options.withCredentials).to.be.false; }); + + it('adds the ortb2 gpp consent info to the request', function () { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const ortb2 = { + regs: { + gpp: 'somegppstring', + gpp_sid: [6, 7] + } + }; + let clonedBidderRequest = {...bidderRequest, ortb2}; + const data = spec.buildRequests(validBidRequests, clonedBidderRequest)[0].data; + expect(data.regs.ext.gpp).to.equal('somegppstring'); + expect(data.regs.ext.gpp_sid).to.eql([6, 7]); + }); }); describe('Endpoint & Impression Request Mode:', () => { @@ -835,6 +851,50 @@ describe('YahooSSP Bid Adapter:', () => { }); }); + describe('User data', () => { + it('should set the allowed sources user eids', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + validBidRequests[0].userIdAsEids = createEidsArray({ + admixerId: 'admixerId_FROM_USER_ID_MODULE', + adtelligentId: 'adtelligentId_FROM_USER_ID_MODULE', + amxId: 'amxId_FROM_USER_ID_MODULE', + britepoolid: 'britepoolid_FROM_USER_ID_MODULE', + deepintentId: 'deepintentId_FROM_USER_ID_MODULE', + publinkId: 'publinkId_FROM_USER_ID_MODULE', + intentIqId: 'intentIqId_FROM_USER_ID_MODULE', + idl_env: 'idl_env_FROM_USER_ID_MODULE', + imuid: 'imuid_FROM_USER_ID_MODULE', + criteoId: 'criteoId_FROM_USER_ID_MODULE', + fabrickId: 'fabrickId_FROM_USER_ID_MODULE', + }); + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + + expect(data.user.ext.eids).to.deep.equal([ + {source: 'admixer.net', uids: [{id: 'admixerId_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'adtelligent.com', uids: [{id: 'adtelligentId_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'amxdt.net', uids: [{id: 'amxId_FROM_USER_ID_MODULE', atype: 1}]}, + {source: 'britepool.com', uids: [{id: 'britepoolid_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'deepintent.com', uids: [{id: 'deepintentId_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'epsilon.com', uids: [{id: 'publinkId_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'intentiq.com', uids: [{id: 'intentIqId_FROM_USER_ID_MODULE', atype: 1}]}, + {source: 'liveramp.com', uids: [{id: 'idl_env_FROM_USER_ID_MODULE', atype: 3}]}, + {source: 'intimatemerger.com', uids: [{id: 'imuid_FROM_USER_ID_MODULE', atype: 1}]}, + {source: 'criteo.com', uids: [{id: 'criteoId_FROM_USER_ID_MODULE', atype: 1}]}, + {source: 'neustar.biz', uids: [{id: 'fabrickId_FROM_USER_ID_MODULE', atype: 1}]} + ]); + }); + + it('should not set not allowed user eids sources', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + validBidRequests[0].userIdAsEids = createEidsArray({ + justId: 'justId_FROM_USER_ID_MODULE' + }); + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + + expect(data.user.ext.eids).to.deep.equal([]); + }); + }); + describe('Request Payload oRTB bid validation:', () => { it('should generate a valid openRTB bid-request object in the data field', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index eee53771a80..df91100b966 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -1,15 +1,14 @@ -import {assert, expect} from 'chai'; -import {spec} from 'modules/yandexBidAdapter.js'; -import {parseUrl} from 'src/utils.js'; -import {BANNER} from '../../../src/mediaTypes'; +import { assert, expect } from 'chai'; +import { spec } from 'modules/yandexBidAdapter.js'; +import { parseUrl } from 'src/utils.js'; +import { BANNER } from '../../../src/mediaTypes'; describe('Yandex adapter', function () { function getBidConfig() { return { bidder: 'yandex', params: { - pageId: 123, - impId: 1, + placementId: '123-1', }, }; } @@ -32,7 +31,7 @@ describe('Yandex adapter', function () { describe('isBidRequestValid', function () { it('should return true when required params found', function () { - const bid = getBidConfig(); + const bid = getBidRequest(); assert(spec.isBidRequestValid(bid)); }); @@ -40,18 +39,28 @@ describe('Yandex adapter', function () { expect(spec.isBidRequestValid({})).to.be.false; }); - it('should return false when required params.pageId are not passed', function () { + it('should return false when required params.placementId are not passed', function () { + const bid = getBidConfig(); + delete bid.params.placementId; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when required params.placementId are not valid', function () { const bid = getBidConfig(); - delete bid.params.pageId; + bid.params.placementId = '123'; - expect(spec.isBidRequestValid(bid)).to.be.false + expect(spec.isBidRequestValid(bid)).to.be.false; }); - it('should return false when required params.impId are not passed', function () { + it('should return true when passed deprecated placement config', function () { const bid = getBidConfig(); - delete bid.params.impId; + delete bid.params.placementId; + + bid.params.pageId = 123; + bid.params.impId = 1; - expect(spec.isBidRequestValid(bid)).to.be.false + expect(spec.isBidRequestValid(bid)); }); }); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 4c39fe5fe29..e5151cf789c 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -1,7 +1,7 @@ import { config } from 'src/config.js'; -import { expect } from 'chai' -import { spec } from 'modules/yieldlabBidAdapter.js' -import { newBidder } from 'src/adapters/bidderFactory.js' +import { expect } from 'chai'; +import { spec } from 'modules/yieldlabBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; const DEFAULT_REQUEST = () => ({ bidder: 'yieldlab', @@ -11,11 +11,11 @@ const DEFAULT_REQUEST = () => ({ targeting: { key1: 'value1', key2: 'value2', - notDoubleEncoded: 'value3,value4' + notDoubleEncoded: 'value3,value4', }, customParams: { extraParam: true, - foo: 'bar' + foo: 'bar', }, extId: 'abc', iabContent: { @@ -31,8 +31,8 @@ const DEFAULT_REQUEST = () => ({ cat: ['cat1', 'cat2,ppp', 'cat3|||//'], context: '7', keywords: ['k1,', 'k2..'], - live: '0' - } + live: '0', + }, }, bidderRequestId: '143346cf0f1731', auctionId: '2e41f65424c87c', @@ -43,8 +43,14 @@ const DEFAULT_REQUEST = () => ({ source: 'netid.de', uids: [{ id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - atype: 1 - }] + atype: 1, + }], + }, { + source: 'digitrust.de', + uids: [{ + id: 'd8aa10fa-d86c-451d-aad8-5f16162a9e64', + atype: 2, + }], }], schain: { ver: '1.0', @@ -53,32 +59,107 @@ const DEFAULT_REQUEST = () => ({ { asi: 'indirectseller.com', sid: '1', - hp: 1 + hp: 1, }, { asi: 'indirectseller2.com', name: 'indirectseller2 name with comma , and bang !', sid: '2', - hp: 1 - } - ] - } -}) + hp: 1, + }, + ], + }, +}); const VIDEO_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { mediaTypes: { video: { playerSize: [[640, 480]], - context: 'instream' - } - } -}) + context: 'instream', + }, + }, +}); const NATIVE_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { mediaTypes: { - native: {} - } -}) + native: {}, + }, +}); + +const IAB_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { + params: { + adslotId: '1111', + supplyId: '2222', + iabContent: { + id: 'foo', + episode: '99', + title: 'bar', + series: 'baz', + season: 's01', + artist: 'foobar', + genre: 'barbaz', + isrc: 'CC-XXX-YY-NNNNN', + url: 'https://foo.test', + cat: ['cat1', 'cat2,ppp', 'cat3|||//'], + context: '2', + keywords: ['k1', 'k2', 'k3', 'k4'], + live: '0', + album: 'foo', + cattax: '3', + prodq: 2, + contentrating: 'foo', + userrating: 'bar', + qagmediarating: 2, + sourcerelationship: 1, + len: 12345, + language: 'en', + embeddable: 0, + producer: { + id: 'foo', + name: 'bar', + cattax: 532, + cat: [1, 'foo', true], + domain: 'producer.test', + }, + data: { + id: 'foo', + name: 'bar', + segment: [{ + name: 'foo', + value: 'bar', + ext: { + foo: { + bar: 'bar', + }, + }, + }, { + name: 'foo2', + value: 'bar2', + ext: { + test: { + nums: { + int: 123, + float: 123.123, + }, + bool: true, + string: 'foo2', + }, + }, + }], + }, + network: { + id: 'foo', + name: 'bar', + domain: 'network.test', + }, + channel: { + id: 'bar', + name: 'foo', + domain: 'channel.test', + }, + }, + }, +}); const RESPONSE = { advertiser: 'yieldlab', @@ -88,177 +169,231 @@ const RESPONSE = { price: 1, pid: 2222, adsize: '728x90', - adtype: 'BANNER' -} + adtype: 'BANNER', +}; const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { adtype: 'NATIVE', native: { link: { - url: 'https://www.yieldlab.de' + url: 'https://www.yieldlab.de', }, assets: [ { id: 1, title: { - text: 'This is a great headline' - } + text: 'This is a great headline', + }, }, { id: 2, img: { url: 'https://localhost:8080/yl-logo100x100.jpg', w: 100, - h: 100 - } + h: 100, + }, }, { id: 3, data: { - value: 'Native body value' - } - } + value: 'Native body value', + }, + }, ], imptrackers: [ 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', 'http://localhost:8080/md/1111/9efa4e76-2030-4f04-bb9f-322541f8d611?mdata=false&pvid=false&ids=x:1', - 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0' - ] - } -}) + 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0', + ], + }, +}); const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { - adtype: 'VIDEO' -}) + adtype: 'VIDEO', +}); const PVID_RESPONSE = Object.assign({}, VIDEO_RESPONSE, { - pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c' -}) + pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c', +}); const REQPARAMS = { json: true, - ts: 1234567890 -} + ts: 1234567890, +}; const REQPARAMS_GDPR = Object.assign({}, REQPARAMS, { gdpr: true, - consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA' -}) + consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', +}); const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { - iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0' -}) + iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0', +}); describe('yieldlabBidAdapter', () => { describe('instantiation from spec', () => { it('is working properly', () => { - const yieldlabBidAdapter = newBidder(spec) - expect(yieldlabBidAdapter.callBids).to.exist.and.to.be.a('function') - }) - }) + const yieldlabBidAdapter = newBidder(spec); + expect(yieldlabBidAdapter.callBids).to.exist.and.to.be.a('function'); + }); + }); describe('isBidRequestValid', () => { it('should return true when all required parameters are found', () => { const request = { params: { adslotId: '1111', - supplyId: '2222' - } - } - expect(spec.isBidRequestValid(request)).to.equal(true) - }) + supplyId: '2222', + }, + }; + expect(spec.isBidRequestValid(request)).to.equal(true); + }); it('should return false when required parameters are missing', () => { - expect(spec.isBidRequestValid({})).to.equal(false) - }) - }) + expect(spec.isBidRequestValid({})).to.equal(false); + }); + }); describe('buildRequests', () => { - const bidRequests = [DEFAULT_REQUEST()] + const bidRequests = [DEFAULT_REQUEST()]; describe('default functionality', () => { - let request + let request; before(() => { - request = spec.buildRequests(bidRequests) - }) + request = spec.buildRequests(bidRequests); + }); it('sends bid request to ENDPOINT via GET', () => { - expect(request.method).to.equal('GET') - }) + expect(request.method).to.equal('GET'); + }); it('returns a list of valid requests', () => { - expect(request.validBidRequests).to.eql(bidRequests) - }) + expect(request.validBidRequests).to.eql(bidRequests); + }); it('passes single-encoded targeting to bid request', () => { - expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') - }) + expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4'); + }); it('passes userids to bid request', () => { - expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg') - }) + expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg%2Cdigitrust.de%3Ad8aa10fa-d86c-451d-aad8-5f16162a9e64'); + }); + + it('passes atype to bid request', () => { + expect(request.url).to.include('atypes=netid.de%3A1%2Cdigitrust.de%3A2'); + }); it('passes extra params to bid request', () => { - expect(request.url).to.include('extraParam=true&foo=bar') - }) + expect(request.url).to.include('extraParam=true&foo=bar'); + }); it('passes unencoded schain string to bid request', () => { - expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') - }) + expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,'); + }); it('passes iab_content string to bid request', () => { - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') - }) + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0'); + }); it('passes correct size to bid request', () => { - expect(request.url).to.include('728x90') - }) + expect(request.url).to.include('728x90'); + }); it('passes external id to bid request', () => { - expect(request.url).to.include('id=abc') - }) - }) + expect(request.url).to.include('id=abc'); + }); + }); describe('iab_content handling', () => { const siteConfig = { ortb2: { site: { content: { - id: 'id_from_config' - } - } - } - } + id: 'id_from_config', + }, + }, + }, + }; beforeEach(() => { - config.setConfig(siteConfig) - }) + config.setConfig(siteConfig); + }); afterEach(() => { - config.resetConfig() - }) + config.resetConfig(); + }); it('generates iab_content string from bidder params', () => { - const request = spec.buildRequests(bidRequests) - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') - }) + const request = spec.buildRequests(bidRequests); + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0'); + }); it('generates iab_content string from first party data if not provided in bidder params', () => { - const requestWithoutIabContent = DEFAULT_REQUEST() - delete requestWithoutIabContent.params.iabContent - - const request = spec.buildRequests([{...requestWithoutIabContent, ...siteConfig}]) - expect(request.url).to.include('iab_content=id%3Aid_from_config') - }) - }) + const requestWithoutIabContent = DEFAULT_REQUEST(); + delete requestWithoutIabContent.params.iabContent; + + const request = spec.buildRequests([{...requestWithoutIabContent, ...siteConfig}]); + expect(request.url).to.include('iab_content=id%3Aid_from_config'); + }); + + it('flattens the iabContent, encodes the values, joins the keywords into one value, and than encodes the iab_content request param ', () => { + const expectedIabContentValue = encodeURIComponent( + 'id:foo,' + + 'episode:99,' + + 'title:bar,' + + 'series:baz,' + + 'season:s01,' + + 'artist:foobar,' + + 'genre:barbaz,' + + 'isrc:CC-XXX-YY-NNNNN,' + + 'url:https%3A%2F%2Ffoo.test,' + + 'cat:cat1|cat2%2Cppp|cat3%7C%7C%7C%2F%2F,' + + 'context:2,' + + 'keywords:k1|k2|k3|k4,' + + 'live:0,' + + 'album:foo,' + + 'cattax:3,' + + 'prodq:2,' + + 'contentrating:foo,' + + 'userrating:bar,' + + 'qagmediarating:2,' + + 'sourcerelationship:1,' + + 'len:12345,' + + 'language:en,' + + 'embeddable:0,' + + 'producer.id:foo,' + + 'producer.name:bar,' + + 'producer.cattax:532,' + + 'cat:1|foo|true,' + + 'producer.domain:producer.test,' + + 'data.id:foo,data.name:bar,' + + 'data.segment.0.name:foo,' + + 'data.segment.0.value:bar,' + + 'data.segment.0.ext.foo.bar:bar,' + + 'data.segment.1.name:foo2,' + + 'data.segment.1.value:bar2,' + + 'data.segment.1.ext.test.nums.int:123,' + + 'data.segment.1.ext.test.nums.float:123.123,' + + 'data.segment.1.ext.test.bool:true,' + + 'data.segment.1.ext.test.string:foo2,' + + 'network.id:foo,network.name:bar,' + + 'network.domain:network.test,' + + 'channel.id:bar,' + + 'channel.name:foo,' + + 'channel.domain:channel.test' + ); + const request = spec.buildRequests([IAB_REQUEST()], REQPARAMS); + expect(request.url).to.include('iab_content=' + expectedIabContentValue); + }); + }); it('passes unencoded schain string to bid request when complete == 0', () => { - const schainRequest = DEFAULT_REQUEST() + const schainRequest = DEFAULT_REQUEST(); schainRequest.schain.complete = 0; // - const request = spec.buildRequests([schainRequest]) - expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') - }) + const request = spec.buildRequests([schainRequest]); + expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,'); + }); it('passes encoded referer to bid request', () => { const refererRequest = spec.buildRequests(bidRequests, { @@ -267,123 +402,123 @@ describe('yieldlabBidAdapter', () => { numIframes: 0, reachedTop: true, page: 'https://www.yieldlab.de/test?with=querystring', - stack: ['https://www.yieldlab.de/test?with=querystring'] - } - }) + stack: ['https://www.yieldlab.de/test?with=querystring'], + }, + }); - expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') - }) + expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring'); + }); it('passes gdpr flag and consent if present', () => { const gdprRequest = spec.buildRequests(bidRequests, { gdprConsent: { consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', - gdprApplies: true - } - }) + gdprApplies: true, + }, + }); - expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA') - expect(gdprRequest.url).to.include('gdpr=true') - }) + expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(gdprRequest.url).to.include('gdpr=true'); + }); describe('sizes handling', () => { it('passes correct size to bid request for mediaType banner', () => { const bannerRequest = DEFAULT_REQUEST(); bannerRequest.mediaTypes = { banner: { - sizes: [[123, 456]] - } - } + sizes: [[123, 456]], + }, + }; // when mediaTypes is present it has precedence over the sizes field (728, 90) - let request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('sizes') - expect(request.url).to.include('123x456') + let request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('sizes'); + expect(request.url).to.include('123x456'); - bannerRequest.mediaTypes.banner.sizes = [123, 456] - request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('123x456') + bannerRequest.mediaTypes.banner.sizes = [123, 456]; + request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('123x456'); - bannerRequest.mediaTypes.banner.sizes = [[123, 456], [320, 240]] - request = spec.buildRequests([bannerRequest], REQPARAMS) - expect(request.url).to.include('123x456') - expect(request.url).to.include('320x240') - }) + bannerRequest.mediaTypes.banner.sizes = [[123, 456], [320, 240]]; + request = spec.buildRequests([bannerRequest], REQPARAMS); + expect(request.url).to.include('123x456'); + expect(request.url).to.include('320x240'); + }); it('passes correct sizes to bid request when mediaType is not present', () => { // information is taken from the top level sizes field const sizesRequest = DEFAULT_REQUEST(); - let request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('sizes') - expect(request.url).to.include('728x90') + let request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('sizes'); + expect(request.url).to.include('728x90'); - sizesRequest.sizes = [[728, 90]] - request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('728x90') + sizesRequest.sizes = [[728, 90]]; + request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('728x90'); - sizesRequest.sizes = [[728, 90], [320, 240]] - request = spec.buildRequests([sizesRequest], REQPARAMS) - expect(request.url).to.include('728x90') - }) + sizesRequest.sizes = [[728, 90], [320, 240]]; + request = spec.buildRequests([sizesRequest], REQPARAMS); + expect(request.url).to.include('728x90'); + }); it('does not pass the sizes parameter for mediaType video', () => { const videoRequest = VIDEO_REQUEST(); - let request = spec.buildRequests([videoRequest], REQPARAMS) - expect(request.url).to.not.include('sizes') - }) + let request = spec.buildRequests([videoRequest], REQPARAMS); + expect(request.url).to.not.include('sizes'); + }); it('does not pass the sizes parameter for mediaType native', () => { const nativeRequest = NATIVE_REQUEST(); - let request = spec.buildRequests([nativeRequest], REQPARAMS) - expect(request.url).to.not.include('sizes') - }) - }) - }) + let request = spec.buildRequests([nativeRequest], REQPARAMS); + expect(request.url).to.not.include('sizes'); + }); + }); + }); describe('interpretResponse', () => { - let bidRequest + let bidRequest; before(() => { - bidRequest = DEFAULT_REQUEST() - }) + bidRequest = DEFAULT_REQUEST(); + }); it('handles nobid responses', () => { - expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) - expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) - }) + expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0); + expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0); + }); it('should get correct bid response', () => { - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS}) - - expect(result[0].requestId).to.equal('2d925f27f5079f') - expect(result[0].cpm).to.equal(0.01) - expect(result[0].width).to.equal(728) - expect(result[0].height).to.equal(90) - expect(result[0].creativeId).to.equal('1111') - expect(result[0].dealId).to.equal(2222) - expect(result[0].currency).to.equal('EUR') - expect(result[0].netRevenue).to.equal(false) - expect(result[0].ttl).to.equal(300) - expect(result[0].referrer).to.equal('') - expect(result[0].meta.advertiserDomains).to.equal('yieldlab') - expect(result[0].ad).to.include('', + privacyLink: 'https://privacy-link.example', ext: { foo: 'foo-value', baz: 'baz-value', @@ -98,9 +100,18 @@ const ortbBid = { privacy: 'https://privacy-link.example', ver: '1.2' } - }, + } }; +const completeNativeBid = { + adId: '123', + transactionId: 'au', + native: { + ...bid.native, + ...ortbBid.native + } +} + const ortbRequest = { assets: [ { @@ -224,6 +235,14 @@ describe('native.js', function () { expect(targeting.hb_native_baz).to.equal('hb_native_baz:123'); }); + it('sends placeholdes targetings with ortb native response', function () { + const targeting = getNativeTargeting(completeNativeBid); + + expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal('Native Creative'); + expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('Cool description great stuff'); + expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('https://www.link.example'); + }); + it('should only include native targeting keys with values', function () { const adUnit = { transactionId: 'au', @@ -302,6 +321,10 @@ describe('native.js', function () { required: false, sendTargetingKeys: false, }, + privacyLink: { + required: false, + sendTargetingKeys: false, + }, ext: { foo: { required: false, @@ -348,6 +371,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.icon, CONSTANTS.NATIVE_KEYS.sponsoredBy, CONSTANTS.NATIVE_KEYS.clickUrl, + CONSTANTS.NATIVE_KEYS.privacyLink, CONSTANTS.NATIVE_KEYS.rendererUrl, ]); @@ -380,6 +404,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.icon, CONSTANTS.NATIVE_KEYS.sponsoredBy, CONSTANTS.NATIVE_KEYS.clickUrl, + CONSTANTS.NATIVE_KEYS.privacyLink, ]); expect(bid.native.adTemplate).to.deep.equal( @@ -437,9 +462,9 @@ describe('native.js', function () { adId: '123', }; - const message = getAllAssetsMessage(messageRequest, bid, {getNativeReq: () => null}); + const message = getAllAssetsMessage(messageRequest, bid); - expect(message.assets.length).to.equal(9); + expect(message.assets.length).to.equal(10); expect(message.assets).to.deep.include({ key: 'body', value: bid.native.body, @@ -485,7 +510,7 @@ describe('native.js', function () { adId: '123', }; - const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields, {getNativeReq: () => null}); + const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); expect(message.assets.length).to.equal(4); expect(message.assets).to.deep.include({ @@ -506,16 +531,16 @@ describe('native.js', function () { }); }); - it('creates native all asset message with OpenRTB format', function () { + it('creates native all asset message with complete format', function () { const messageRequest = { message: 'Prebid Native', action: 'allAssetRequest', adId: '123', }; - const message = getAllAssetsMessage(messageRequest, ortbBid, {getNativeReq: () => ortbRequest}); + const message = getAllAssetsMessage(messageRequest, completeNativeBid); - expect(message.assets.length).to.equal(8); + expect(message.assets.length).to.equal(10); expect(message.assets).to.deep.include({ key: 'body', value: bid.native.body, @@ -548,6 +573,14 @@ describe('native.js', function () { key: 'privacyLink', value: ortbBid.native.ortb.privacy, }); + expect(message.assets).to.deep.include({ + key: 'foo', + value: bid.native.ext.foo, + }); + expect(message.assets).to.deep.include({ + key: 'baz', + value: bid.native.ext.baz, + }); }); const SAMPLE_ORTB_REQUEST = toOrtbNativeRequest({ @@ -555,60 +588,39 @@ describe('native.js', function () { body: 'vbody' }); const SAMPLE_ORTB_RESPONSE = { - native: { - ortb: { - link: { - url: 'url' - }, - assets: [ - { - id: 0, - title: { - text: 'vtitle' - } - }, - { - id: 1, - data: { - value: 'vbody' - } - } - ] + link: { + url: 'url' + }, + assets: [ + { + id: 0, + title: { + text: 'vtitle' + } + }, + { + id: 1, + data: { + value: 'vbody' + } } - } + ], + eventtrackers: [ + { event: 1, method: 1, url: 'https://sampleurl.com' }, + { event: 1, method: 2, url: 'https://sampleurljs.com' } + ] } - describe('getAllAssetsMessage', () => { + describe('toLegacyResponse', () => { it('returns assets in legacy format for ortb responses', () => { - const actual = getAllAssetsMessage({}, SAMPLE_ORTB_RESPONSE, {getNativeReq: () => SAMPLE_ORTB_REQUEST}); - expect(actual.assets).to.eql([ - { - key: 'clickUrl', - value: 'url' - }, - { - key: 'title', - value: 'vtitle' - }, - { - key: 'body', - value: 'vbody' - }, - ]) + const actual = toLegacyResponse(SAMPLE_ORTB_RESPONSE, SAMPLE_ORTB_REQUEST); + expect(actual.body).to.equal('vbody'); + expect(actual.title).to.equal('vtitle'); + expect(actual.clickUrl).to.equal('url'); + expect(actual.javascriptTrackers).to.equal(''); + expect(actual.impressionTrackers.length).to.equal(1); + expect(actual.impressionTrackers[0]).to.equal('https://sampleurl.com'); }); }); - describe('getAssetsMessage', () => { - Object.entries({ - 'hb_native_title': {key: 'title', value: 'vtitle'}, - 'hb_native_body': {key: 'body', value: 'vbody'} - }).forEach(([tkey, assetVal]) => { - it(`returns ${tkey} asset in legacy format for ortb responses`, () => { - const actual = getAssetMessage({ - assets: [tkey] - }, SAMPLE_ORTB_RESPONSE, {getNativeReq: () => SAMPLE_ORTB_REQUEST}) - expect(actual.assets).to.eql([assetVal]) - }) - }) - }) }); describe('validate native openRTB', function () { diff --git a/test/spec/ortb2.5StrictTranslator/dsl_spec.js b/test/spec/ortb2.5StrictTranslator/dsl_spec.js new file mode 100644 index 00000000000..c9b4575bcd2 --- /dev/null +++ b/test/spec/ortb2.5StrictTranslator/dsl_spec.js @@ -0,0 +1,137 @@ +import {Arr, ERR_ENUM, ERR_TYPE, ERR_UNKNOWN_FIELD, IntEnum, Obj} from '../../../libraries/ortb2.5StrictTranslator/dsl.js'; +import {deepClone} from '../../../src/utils.js'; + +describe('DSL', () => { + const spec = (() => { + const inner = Obj(['p21', 'p22'], { + enum: IntEnum(10, 20), + enumArray: Arr(IntEnum(10, 20)) + }); + return Obj(['p11', 'p12'], { + inner, + innerArray: Arr(inner) + }); + })(); + + let onError; + + function scan(obj) { + spec(null, null, null, obj, onError); + } + + beforeEach(() => { + onError = sinon.stub(); + }); + + it('checks object type', () => { + scan(null); + sinon.assert.calledWith(onError, ERR_TYPE, null, null, null, null); + }); + it('ignores known fields and ext', () => { + scan({p11: 1, p12: 2, ext: {e1: 1, e2: 2}}); + sinon.assert.notCalled(onError); + }); + it('detects unknown fields', () => { + const obj = {p11: 1, unk: 2}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_UNKNOWN_FIELD, 'unk', obj, 'unk', 2); + }); + describe('when nested', () => { + describe('directly', () => { + it('detects unknown fields', () => { + const obj = {inner: {p21: 1, unk: 2}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_UNKNOWN_FIELD, 'inner.unk', obj.inner, 'unk', 2); + }); + it('accepts enum values in range', () => { + scan({inner: {enum: 12}}); + sinon.assert.notCalled(onError); + }); + [Infinity, NaN, -Infinity].forEach(val => { + it(`does not accept ${val} in enum`, () => { + const obj = {inner: {enum: val}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_ENUM, 'inner.enum', obj.inner, 'enum', val); + }); + }); + it('accepts arrays of enums that are in range', () => { + scan({inner: {enumArray: [12, 13]}}); + sinon.assert.notCalled(onError); + }) + it('detects enum values out of range', () => { + const obj = {inner: {enum: -1}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_ENUM, 'inner.enum', obj.inner, 'enum', -1); + }); + it('detects enum values that are not numbers', () => { + const obj = {inner: {enum: 'err'}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_TYPE, 'inner.enum', obj.inner, 'enum', 'err'); + }) + it('detects arrays of enums that are out of range', () => { + const obj = {inner: {enumArray: [12, 13, -1, 14]}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_ENUM, 'inner.enumArray.2', obj.inner.enumArray, 2, -1); + }); + it('detects when enum arrays are not arrays', () => { + const obj = {inner: {enumArray: 'err'}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_TYPE, 'inner.enumArray', obj.inner, 'enumArray', 'err'); + }); + it('detects items within enum arrays that are not numbers', () => { + const obj = {inner: {enumArray: [12, 'err', 13]}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_TYPE, 'inner.enumArray.1', obj.inner.enumArray, 1, 'err'); + }) + }); + describe('into arrays', () => { + it('detects if inner array is not an array', () => { + const obj = {innerArray: 'err', inner: {p21: 1}}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_TYPE, 'innerArray', obj, 'innerArray', 'err'); + }); + it('detects when elements of inner array are not objects', () => { + const obj = {innerArray: [{p21: 1}, 'err', {ext: {r: 1}}]}; + scan(obj); + sinon.assert.calledOnce(onError); + sinon.assert.calledWith(onError, ERR_TYPE, 'innerArray.1', obj.innerArray, 1, 'err'); + }); + const oos = { + innerArray: [ + {p22: 2, unk: 3, enumArray: [-1, 12, 'err']}, + {p21: 1, enum: -1, ext: {e: 1}}, + ] + }; + it('detects invalid properties within inner array', () => { + const obj = deepClone(oos); + scan(obj); + sinon.assert.calledWith(onError, ERR_UNKNOWN_FIELD, 'innerArray.0.unk', obj.innerArray[0], 'unk', 3); + sinon.assert.calledWith(onError, ERR_ENUM, 'innerArray.0.enumArray.0', obj.innerArray[0].enumArray, 0, -1); + sinon.assert.calledWith(onError, ERR_TYPE, 'innerArray.0.enumArray.2', obj.innerArray[0].enumArray, 2, 'err'); + sinon.assert.calledWith(onError, ERR_ENUM, 'innerArray.1.enum', obj.innerArray[1], 'enum', -1); + }); + it('can remove all invalid properties during scan', () => { + onError.callsFake((errno, path, obj, field) => { + Array.isArray(obj) ? obj.splice(field, 1) : delete obj[field]; + }); + const obj = deepClone(oos); + scan(obj); + expect(obj).to.eql({ + innerArray: [ + {p22: 2, enumArray: [12]}, + {p21: 1, ext: {e: 1}} + ] + }); + }) + }) + }) +}); diff --git a/test/spec/ortb2.5StrictTranslator/spec_spec.js b/test/spec/ortb2.5StrictTranslator/spec_spec.js new file mode 100644 index 00000000000..a54b551bf61 --- /dev/null +++ b/test/spec/ortb2.5StrictTranslator/spec_spec.js @@ -0,0 +1,358 @@ +import {BidRequest} from '../../../libraries/ortb2.5StrictTranslator/spec.js'; + +// sample requests taken from ORTB 2.5 spec: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf +const SAMPLE_REQUESTS = [ + { + 'id': '80ce30c53c16e6ede735f123ef6e32361bfc7b22', + 'at': 1, + 'cur': ['USD'], + 'imp': [ + { + 'id': '1', + 'bidfloor': 0.03, + 'banner': { + 'h': 250, 'w': 300, 'pos': 0 + } + } + ], + 'site': { + 'id': '102855', + 'cat': ['IAB3-1'], + 'domain': 'www.foobar.com', + 'page': 'http://www.foobar.com/1234.html ', + 'publisher': { + 'id': '8953', + 'name': 'foobar.com', + 'cat': ['IAB3-1'], + 'domain': 'foobar.com' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2', + 'ip': '123.145.167.10' + }, + 'user': { + 'id': '55816b39711f9b5acf3b90e313ed29e51665623f' + } + }, + { + 'id': '123456789316e6ede735f123ef6e32361bfc7b22', + 'at': 2, + 'cur': ['USD'], + 'imp': [ + { + 'id': '1', + 'bidfloor': 0.03, + 'iframebuster': ['vendor1.com', 'vendor2.com'], + 'banner': { + 'h': 250, + 'w': 300, + 'pos': 0, + 'battr': [13], + 'expdir': [2, 4] + } + } + ], + 'site': { + 'id': '102855', + 'cat': ['IAB3-1'], + 'domain': 'www.foobar.com', + 'page': 'http://www.foobar.com/1234.html', + 'publisher': { + 'id': '8953', + 'name': 'foobar.com', + 'cat': ['IAB3-1'], + 'domain': 'foobar.com' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2', + 'ip': '123.145.167.10' + }, + 'user': { + 'id': '55816b39711f9b5acf3b90e313ed29e51665623f', + 'buyeruid': '545678765467876567898765678987654', + 'data': [ + { + 'id': '6', + 'name': 'Data Provider 1', + 'segment': [ + { + 'id': '12341318394918', 'name': 'auto intenders' + }, + { + 'id': '1234131839491234', 'name': 'auto enthusiasts' + }, + { + 'id': '23423424', + 'name': 'data-provider1-age', + 'value': '30-40' + } + ] + } + ] + } + }, + { + 'id': 'IxexyLDIIk', + 'at': 2, + 'bcat': ['IAB25', 'IAB7-39', 'IAB8-18', 'IAB8-5', 'IAB9-9'], + 'badv': ['apple.com', 'go-text.me', 'heywire.com'], + 'imp': [ + { + 'id': '1', + 'bidfloor': 0.5, + 'instl': 0, + 'tagid': 'agltb3B1Yi1pbmNyDQsSBFNpdGUY7fD0FAw', + 'banner': { + 'w': 728, + 'h': 90, + 'pos': 1, + 'btype': [4], + 'battr': [14], + 'api': [3] + } + } + ], + 'app': { + 'id': 'agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA', + 'name': 'Yahoo Weather', + 'cat': ['IAB15', 'IAB15-10'], + 'ver': '1.0.2', + 'bundle': '12345', + 'storeurl': 'https://itunes.apple.com/id628677149', + 'publisher': { + 'id': 'agltb3B1Yi1pbmNyDAsSA0FwcBiJkfTUCV', + 'name': 'yahoo', + 'domain': 'www.yahoo.com' + } + }, + 'device': { + 'dnt': 0, + 'ua': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3', + 'ip': '123.145.167.189', + 'ifa': 'AA000DFE74168477C70D291f574D344790E0BB11', + 'carrier': 'VERIZON', + 'language': 'en', + 'make': 'Apple', + 'model': 'iPhone', + 'os': 'iOS', + 'osv': '6.1', + 'js': 1, + 'connectiontype': 3, + 'devicetype': 1, + 'geo': { + 'lat': 35.012345, + 'lon': -115.12345, + 'country': 'USA', + 'metro': '803', + 'region': 'CA', + 'city': 'Los Angeles', + 'zip': '90049' + } + }, + 'user': { + 'id': 'ffffffd5135596709273b3a1a07e466ea2bf4fff', + 'yob': 1984, + 'gender': 'M' + } + }, + { + 'id': '1234567893', + 'at': 2, + 'tmax': 120, + 'imp': [ + { + 'id': '1', + 'bidfloor': 0.03, + 'video': { + 'w': 640, + 'h': 480, + 'pos': 1, + 'startdelay': 0, + 'minduration': 5, + 'maxduration': 30, + 'maxextended': 30, + 'minbitrate': 300, + 'maxbitrate': 1500, + 'api': [1, 2], + 'protocols': [2, 3], + 'mimes': [ + 'video/x-flv', + 'video/mp4', + 'application/x-shockwave-flash', + 'application/javascript' + ], + 'linearity': 1, + 'boxingallowed': 1, + 'playbackmethod': [1, 3], + 'delivery': [2], + 'battr': [13, 14], + 'companionad': [ + { + 'id': '1234567893-1', + 'w': 300, + 'h': 250, + 'pos': 1, + 'battr': [13, 14], + 'expdir': [2, 4] + }, + { + 'id': '1234567893-2', + 'w': 728, + 'h': 90, + 'pos': 1, + 'battr': [13, 14] + } + ], + 'companiontype': [1, 2] + } + } + ], + 'site': { + 'id': '1345135123', + 'name': 'Site ABCD', + 'domain': 'siteabcd.com', + 'cat': ['IAB2-1', 'IAB2-2'], + 'page': 'http://siteabcd.com/page.htm', + 'ref': 'http://referringsite.com/referringpage.htm', + 'privacypolicy': 1, + 'publisher': { + 'id': 'pub12345', 'name': 'Publisher A' + }, + 'content': { + 'id': '1234567', + 'series': 'All About Cars', + 'season': '2', + 'episode': 23, + 'title': 'Car Show', + 'cat': ['IAB2-2'], + 'keywords': 'keyword-a,keyword-b,keyword-c' + } + }, + 'device': { + 'ip': '64.124.253.1', + 'ua': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16', + 'os': 'OS X', + 'flashver': '10.1', + 'js': 1 + }, + 'user': { + 'id': '456789876567897654678987656789', + 'buyeruid': '545678765467876567898765678987654', + 'data': [ + { + 'id': '6', + 'name': 'Data Provider 1', + 'segment': [ + { + 'id': '12341318394918', 'name': 'auto intenders' + }, + { + 'id': '1234131839491234', 'name': 'auto enthusiasts' + } + ] + } + ] + } + }, + { + 'id': '80ce30c53c16e6ede735f123ef6e32361bfc7b22', + 'at': 1, + 'cur': ['USD'], + 'imp': [ + { + 'id': '1', + 'bidfloor': 0.03, + 'banner': { + 'h': 250, 'w': 300, 'pos': 0 + }, + 'pmp': { + 'private_auction': 1, + 'deals': [ + { + 'id': 'AB-Agency1-0001', + 'at': 1, + 'bidfloor': 2.5, + 'wseat': ['Agency1'] + }, + { + 'id': 'XY-Agency2-0001', + 'at': 2, + 'bidfloor': 2, + 'wseat': ['Agency2'] + } + ] + } + } + ], + 'site': { + 'id': '102855', + 'domain': 'www.foobar.com', + 'cat': ['IAB3-1'], + 'page': 'http://www.foobar.com/1234.html', + 'publisher': { + 'id': '8953', + 'name': 'foobar.com', + 'cat': ['IAB3-1'], + 'domain': 'foobar.com' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2', + 'ip': '123.145.167.10' + }, + 'user': { + 'id': '55816b39711f9b5acf3b90e313ed29e51665623f' + } + }, +]; + +if (FEATURES.NATIVE) { + SAMPLE_REQUESTS.push({ + 'id': '80ce30c53c16e6ede735f123ef6e32361bfc7b22', + 'at': 1, + 'cur': ['USD'], + 'imp': [ + { + 'id': '1', + 'bidfloor': 0.03, + 'native': { + 'request': '{"native":{"ver":"1.0","assets":[ ... ]}}', + 'ver': '1.0', + 'api': [3], + 'battr': [13, 14] + } + } + ], + 'site': { + 'id': '102855', + 'cat': ['IAB3-1'], + 'domain': 'www.foobar.com', + 'page': 'http://www.foobar.com/1234.html ', + 'publisher': { + 'id': '8953', + 'name': 'foobar.com', + 'cat': ['IAB3-1'], + 'domain': 'foobar.com' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2', + 'ip': '123.145.167.10' + }, + 'user': { + 'id': '55816b39711f9b5acf3b90e313ed29e51665623f' + } + }); +} + +describe('BidRequest spec', () => { + SAMPLE_REQUESTS.forEach((req, i) => { + it(`accepts sample #${i}`, () => { + const onError = sinon.stub(); + BidRequest(null, null, null, req, onError); + sinon.assert.notCalled(onError); + }); + }); +}); diff --git a/test/spec/ortb2.5StrictTranslator/translator_spec.js b/test/spec/ortb2.5StrictTranslator/translator_spec.js new file mode 100644 index 00000000000..4bda3d96235 --- /dev/null +++ b/test/spec/ortb2.5StrictTranslator/translator_spec.js @@ -0,0 +1,16 @@ +import {toOrtb25Strict} from '../../../libraries/ortb2.5StrictTranslator/translator.js'; + +describe('toOrtb25Strict', () => { + let translator; + beforeEach(() => { + translator = sinon.stub().callsFake((o) => o); + }) + it('uses provided translator', () => { + translator.reset(); + translator.callsFake(() => ({id: 'test'})); + expect(toOrtb25Strict(null, translator)).to.eql({id: 'test'}); + }); + it('removes fields out of spec', () => { + expect(toOrtb25Strict({unk: 'field', imp: ['err', {}]}, translator)).to.eql({imp: [{}]}); + }); +}); diff --git a/test/spec/ortb2.5Translator/translator_spec.js b/test/spec/ortb2.5Translator/translator_spec.js new file mode 100644 index 00000000000..db20a8f59be --- /dev/null +++ b/test/spec/ortb2.5Translator/translator_spec.js @@ -0,0 +1,64 @@ +import {EXT_PROMOTIONS, moveRule, splitPath, toOrtb25} from '../../../libraries/ortb2.5Translator/translator.js'; +import {deepAccess, deepClone, deepSetValue} from '../../../src/utils.js'; + +describe('ORTB 2.5 translation', () => { + describe('moveRule', () => { + const rule = moveRule('f1.f2.f3', (prefix, field) => `${prefix}.m1.m2.${field}`); + + function applyRule(rule, obj, del = true) { + obj = deepClone(obj); + const deleter = rule(obj); + if (typeof deleter === 'function' && del) { + deleter(); + } + return obj; + } + + it('returns undef when field is not present', () => { + expect(rule({})).to.eql(undefined); + }); + it('can copy field', () => { + expect(applyRule(rule, {f1: {f2: {f3: 'value'}}}, false)).to.eql({ + f1: { + f2: { + f3: 'value', + m1: {m2: {f3: 'value'}} + } + } + }); + }); + it('can move field', () => { + expect(applyRule(rule, {f1: {f2: {f3: 'value'}}}, true)).to.eql({f1: {f2: {m1: {m2: {f3: 'value'}}}}}); + }); + }); + describe('toOrtb25', () => { + EXT_PROMOTIONS.forEach(path => { + const newPath = (() => { + const [prefix, field] = splitPath(path); + return `${prefix}.ext.${field}`; + })(); + + it(`moves ${path} to ${newPath}`, () => { + const obj = {}; + deepSetValue(obj, path, 'val'); + toOrtb25(obj); + expect(deepAccess(obj, path)).to.eql(undefined); + expect(deepAccess(obj, newPath)).to.eql('val'); + }); + }); + it('moves kwarray into keywords', () => { + expect(toOrtb25({app: {keywords: 'k1,k2', kwarray: ['ka1', 'ka2']}})).to.eql({app: {keywords: 'k1,k2,ka1,ka2'}}); + }); + it('does not choke if kwarray is not an array', () => { + expect(toOrtb25({site: {keywords: 'k1,k2', kwarray: 'err'}})).to.eql({site: {keywords: 'k1,k2'}}); + }); + it('does not choke if keywords is not a string', () => { + expect(toOrtb25({user: {keywords: {}, kwarray: ['ka1', 'ka2']}})).to.eql({ + user: { + keywords: {}, + kwarray: ['ka1', 'ka2'] + } + }); + }); + }); +}); diff --git a/test/spec/ortbConverter/banner_spec.js b/test/spec/ortbConverter/banner_spec.js new file mode 100644 index 00000000000..0f6686283a1 --- /dev/null +++ b/test/spec/ortbConverter/banner_spec.js @@ -0,0 +1,203 @@ +import {fillBannerImp, bannerResponseProcessor} from '../../../libraries/ortbConverter/processors/banner.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; +import {inIframe} from '../../../src/utils.js'; + +const topframe = inIframe() ? 0 : 1; + +describe('pbjs -> ortb banner conversion', () => { + [ + { + t: 'non-banner request', + request: { + mediaTypes: { + video: {} + } + }, + imp: {} + }, + { + t: 'banner with no sizes', + request: { + mediaTypes: { + banner: { + pos: 'pos', + } + } + }, + imp: { + banner: { + topframe, + pos: 'pos', + } + } + }, + { + t: 'single size banner', + request: { + mediaTypes: { + banner: { + sizes: [1, 2], + } + } + }, + imp: { + banner: { + format: [ + {w: 1, h: 2} + ], + topframe, + } + } + }, + { + t: 'multi size banner', + request: { + mediaTypes: { + banner: { + sizes: [[1, 2], [3, 4]] + } + } + }, + imp: { + banner: { + format: [ + {w: 1, h: 2}, + {w: 3, h: 4} + ], + topframe, + } + } + }, + { + t: 'banner with pos param', + request: { + mediaTypes: { + banner: { + sizes: [1, 2], + pos: 'pos' + } + } + }, + imp: { + banner: { + format: [ + {w: 1, h: 2} + ], + pos: 'pos', + topframe, + } + }, + }, + { + t: 'banner with pos 0', + request: { + mediaTypes: { + banner: { + sizes: [1, 2], + pos: 0 + } + } + }, + imp: { + banner: { + format: [ + {w: 1, h: 2} + ], + pos: 0, + topframe, + } + } + } + ].forEach(({t, request, imp}) => { + it(`can convert ${t}`, () => { + const actual = {}; + fillBannerImp(actual, request, {}); + expect(actual).to.eql(imp); + }); + }); + + it('should keep ortb2Imp.banner', () => { + const imp = { + banner: { + someParam: 'someValue' + } + }; + fillBannerImp(imp, {mediaTypes: {banner: {sizes: [1, 2]}}}, {}); + expect(imp.banner.someParam).to.eql('someValue'); + }); + + it('does nothing if context.mediaType is set but is not BANNER', () => { + const imp = {}; + fillBannerImp(imp, {mediaTypes: {banner: {sizes: [1, 2]}}}, {mediaType: VIDEO}); + expect(imp).to.eql({}); + }) +}); + +describe('ortb -> pbjs banner conversion', () => { + let createPixel, seatbid2Banner; + beforeEach(() => { + createPixel = sinon.stub().callsFake((url) => `${url}Pixel`); + seatbid2Banner = bannerResponseProcessor({createPixel}); + }); + + [ + { + t: 'non-banner request', + seatbid: { + adm: 'mockAdm', + nurl: 'mockNurl' + }, + response: { + mediaType: VIDEO + }, + expected: { + mediaType: VIDEO + } + }, + { + t: 'response with both adm and nurl', + seatbid: { + adm: 'mockAdm', + nurl: 'mockUrl' + }, + response: { + mediaType: BANNER, + }, + expected: { + mediaType: BANNER, + ad: 'mockAdmmockUrlPixel' + } + }, + { + t: 'response with just adm', + seatbid: { + adm: 'mockAdm' + }, + response: { + mediaType: BANNER, + }, + expected: { + mediaType: BANNER, + ad: 'mockAdm' + } + }, + { + t: 'response with just nurl', + seatbid: { + nurl: 'mockNurl' + }, + response: { + mediaType: BANNER + }, + expected: { + mediaType: BANNER, + adUrl: 'mockNurl' + } + } + ].forEach(({t, seatbid, response, expected}) => { + it(`can handle ${t}`, () => { + seatbid2Banner(response, seatbid, context); + expect(response).to.eql(expected) + }) + }); +}) diff --git a/test/spec/ortbConverter/composer_spec.js b/test/spec/ortbConverter/composer_spec.js new file mode 100644 index 00000000000..f342df38fde --- /dev/null +++ b/test/spec/ortbConverter/composer_spec.js @@ -0,0 +1,69 @@ +import {compose} from '../../../libraries/ortbConverter/lib/composer.js'; + +describe('compose', () => { + it('runs each component in order of priority', () => { + const order = []; + const components = { + first: { + fn: sinon.stub().callsFake(() => order.push(1)), + }, + second: { + fn: sinon.stub().callsFake(() => order.push(2)), + priority: 10 + }, + third: { + fn: sinon.stub().callsFake(() => order.push(3)), + priority: 5 + } + }; + compose(components)(); + expect(order).to.eql([2, 3, 1]); + }); + + it('passes parameters to each component', () => { + const components = { + first: { + fn: sinon.stub() + }, + second: { + fn: sinon.stub() + } + }; + compose(components)('one', 'two'); + Object.values(components).forEach(comp => { + sinon.assert.calledWith(comp.fn, 'one', 'two') + }) + }) + + it('respects overrides', () => { + const components = { + first: { + fn: sinon.stub() + }, + second: { + fn: sinon.stub() + } + }; + const overrides = { + second: sinon.stub() + } + compose(components, overrides)('one', 'two'); + sinon.assert.calledWith(overrides.second, components.second.fn, 'one', 'two') + }) + + it('disables components when override is false', () => { + const components = { + first: { + fn: sinon.stub(), + }, + second: { + fn: sinon.stub() + } + }; + const overrides = { + second: false + }; + compose(components, overrides)('one', 'two'); + sinon.assert.notCalled(components.second.fn); + }) +}); diff --git a/test/spec/ortbConverter/converter_spec.js b/test/spec/ortbConverter/converter_spec.js new file mode 100644 index 00000000000..e00b46e66da --- /dev/null +++ b/test/spec/ortbConverter/converter_spec.js @@ -0,0 +1,283 @@ +import {ortbConverter} from '../../../libraries/ortbConverter/converter.js'; +import {BID_RESPONSE, IMP, REQUEST, RESPONSE} from '../../../src/pbjsORTB.js'; + +describe('pbjs-ortb converter', () => { + const MOCK_BIDDER_REQUEST = { + id: 'bidderRequest', + bids: [ + { + id: 111 + }, + { + id: 112 + } + ] + } + + const MOCK_ORTB_RESPONSE = { + id: 'response', + seatbid: [ + { + seat: 'mockBidder1', + bid: [ + { + impid: 'imp0' + }, + { + impid: 'imp1' + } + ] + }, + { + seat: 'mockBidder2', + bid: [ + { + impid: 'imp1' + } + ] + } + ] + } + + let processors, reqCnt, impCnt; + + beforeEach(() => { + reqCnt = 0; + impCnt = 0; + processors = { + [REQUEST]: { + req: { + fn(ortbRequest, bidderRequest, context) { + ortbRequest.id = `req${reqCnt++}`; + if (context.ctx) { + ortbRequest.ctx = context.ctx; + } + } + } + }, + [IMP]: { + imp: { + fn(imp, bidRequest, context) { + imp.id = `imp${impCnt++}`; + imp.bidId = bidRequest.id; + if (context.ctx) { + imp.ctx = context.ctx; + } + if (context.reqContext?.ctx) { + imp.reqCtx = context.reqContext?.ctx; + } + } + } + }, + [BID_RESPONSE]: { + resp: { + fn(bidResponse, bid, context) { + bidResponse.impid = bid.impid; + bidResponse.bidId = context.imp.bidId; + if (context.ctx) { + bidResponse.ctx = context.ctx; + } + if (context.reqContext?.ctx) { + bidResponse.reqCtx = context.reqContext?.ctx; + } + } + } + }, + [RESPONSE]: { + resp: { + fn(response, ortbResponse, context) { + response.marker = true; + if (context.ctx) { + response.ctx = context.ctx; + } + } + } + } + } + }); + + function makeConverter(options = {}) { + options = Object.assign({ + processors: () => processors + }, options); + return ortbConverter(options); + } + + it('runs each processor', () => { + const cvt = makeConverter(); + const request = cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST}); + expect(request).to.eql({ + id: 'req0', + imp: [ + {id: 'imp0', bidId: 111}, + {id: 'imp1', bidId: 112} + ] + }); + const response = cvt.fromORTB({request, response: MOCK_ORTB_RESPONSE}); + expect(response.bids).to.eql([{ + impid: 'imp0', + bidId: 111 + }, { + impid: 'imp1', + bidId: 112 + }, { + impid: 'imp1', + bidId: 112 + }]); + expect(response.marker).to.be.true; + }); + + it('fromORTB throws if request was not produced by the same converter', () => { + expect(() => { + makeConverter().fromORTB({ + request: { + imp: [ + { + id: 'imp0' + } + ] + }, + response: MOCK_ORTB_RESPONSE + }) + }).to.throw(); + }); + + it('gives precedence to the bidRequests argument over bidderRequest.bids', () => { + expect(makeConverter().toORTB({bidderRequest: MOCK_BIDDER_REQUEST, bidRequests: [MOCK_BIDDER_REQUEST.bids[0]]})).to.eql({ + id: 'req0', + imp: [ + { + id: 'imp0', + bidId: 111 + } + ] + }) + }); + + it('passes context to every processor', () => { + const cvt = makeConverter(); + const request = cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST, context: {ctx: 'context'}}); + expect(request.ctx).to.equal('context'); + request.imp.forEach(imp => expect(imp.ctx).to.equal('context')); + const response = cvt.fromORTB({request, response: MOCK_ORTB_RESPONSE}); + expect(response.ctx).to.eql('context'); + response.bids.forEach(bidResponse => expect(bidResponse.ctx).to.equal('context')); + }); + + it('passes request context to imp and bidResponse processors', () => { + const cvt = makeConverter(); + const request = cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST, context: {ctx: 'context'}}); + expect(request.imp[0].reqCtx).to.eql('context'); + const response = cvt.fromORTB({request, response: MOCK_ORTB_RESPONSE}); + expect(response.bids[0].reqCtx).to.eql('context'); + }); + + it('allows overriding of imp building with `imp`', () => { + const cvt = makeConverter({ + imp: function (buildImp, bidRequest, context) { + return Object.assign({ + extraArg: bidRequest.id, + extraCtx: context.ctx + }, buildImp(bidRequest, context)); + } + }); + const request = cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST, context: {ctx: 'context'}}); + expect(request.imp.length).to.eql(2); + request.imp.forEach(imp => { + expect(imp.extraArg).to.eql(imp.bidId); + expect(imp.extraCtx).to.eql('context'); + }) + }); + + it('allows filtering imps with `imp`', () => { + const cvt = makeConverter({ + imp(buildImp, bidRequest, context) { + if (bidRequest.id === 112) { + return buildImp(bidRequest, context); + } + } + }); + expect(cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST}).imp.length).to.eql(1); + }); + + it('does not include imps that have no id', () => { + const cvt = makeConverter({ + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + delete imp.id; + return imp; + } + }); + expect(cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST}).imp.length).to.eql(0); + }) + + it('allows overriding of response building with bidResponse', () => { + const cvt = makeConverter({ + bidResponse(buildResponse, bid, context) { + return Object.assign({ + extraArg: context.bidRequest.id, + extraCtx: context.ctx + }, buildResponse(bid, context)); + } + }); + const response = cvt.fromORTB({ + response: MOCK_ORTB_RESPONSE, + request: cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST, context: {ctx: 'context'}}) + }); + + expect(response.bids.length).to.equal(3); + response.bids.forEach(response => { + expect(response.extraArg).to.eql(response.bidId); + expect(response.extraCtx).to.eql('context'); + }); + }); + + it('allows filtering of responses with `bidResponse`', () => { + const cvt = makeConverter({ + bidResponse(buildBidResponse, bid, context) { + if (context.seatbid.seat === 'mockBidder1' && context.imp.id === 'imp0') { + return buildBidResponse(bid, context); + } + } + }); + expect(cvt.fromORTB({ + request: cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST}), + response: MOCK_ORTB_RESPONSE + }).bids.length).to.equal(1); + }); + + it('allows overriding of request building with `request`', () => { + const cvt = makeConverter({ + request(buildRequest, imps, bidderRequest, context) { + return { + request: buildRequest(imps, bidderRequest, context), + extraArg: bidderRequest.id, + extraCtx: context.ctx + } + } + }); + const req = cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST, context: {ctx: 'context'}}); + expect(req.extraArg).to.equal(MOCK_BIDDER_REQUEST.id); + expect(req.extraCtx).to.equal('context'); + expect(req.request.imp.length).to.equal(2); + }); + + it('allows overriding of response building with `response`', () => { + const cvt = makeConverter({ + response(buildResponse, bidResponses, ortbResponse, context) { + return { + response: buildResponse(bidResponses, ortbResponse, context), + extraArg: ortbResponse.id, + extraCtx: context.ctx + } + } + }); + const resp = cvt.fromORTB({ + request: cvt.toORTB({bidderRequest: MOCK_BIDDER_REQUEST, context: {ctx: 'context'}}), + response: MOCK_ORTB_RESPONSE, + }); + expect(resp.extraArg).to.equal(MOCK_ORTB_RESPONSE.id); + expect(resp.extraCtx).to.equal('context'); + expect(resp.response.bids.length).to.equal(3); + }) +}) diff --git a/test/spec/ortbConverter/currency_spec.js b/test/spec/ortbConverter/currency_spec.js new file mode 100644 index 00000000000..76f81223f27 --- /dev/null +++ b/test/spec/ortbConverter/currency_spec.js @@ -0,0 +1,40 @@ +import {config} from 'src/config.js'; +import {setOrtbCurrency} from '../../../modules/currency.js'; + +describe('pbjs -> ortb currency', () => { + before(() => { + config.resetConfig(); + }); + + afterEach(() => { + config.resetConfig(); + }); + + it('does not set cur by default', () => { + const req = {}; + setOrtbCurrency(req, {}, {}); + expect(req).to.eql({}); + }); + + it('sets currency based on config', () => { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + const req = {}; + setOrtbCurrency(req, {}, {}); + expect(req.cur).to.eql(['EUR']); + }); + + it('sets currency based on context', () => { + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + const req = {}; + setOrtbCurrency(req, {}, {currency: 'JPY'}); + expect(req.cur).to.eql(['JPY']); + }) +}); diff --git a/test/spec/ortbConverter/default_processors_spec.js b/test/spec/ortbConverter/default_processors_spec.js new file mode 100644 index 00000000000..48204b2c861 --- /dev/null +++ b/test/spec/ortbConverter/default_processors_spec.js @@ -0,0 +1,23 @@ +import {onlyOneClientSection} from '../../../libraries/ortbConverter/processors/default.js'; + +describe('onlyOneClientSection', () => { + [ + [['app'], 'app'], + [['site'], 'site'], + [['dooh'], 'dooh'], + [['app', 'site'], 'app'], + [['dooh', 'app', 'site'], 'dooh'], + [['dooh', 'site'], 'dooh'] + ].forEach(([sections, winner]) => { + it(`should leave only ${winner} in request when it contains ${sections.join(', ')}`, () => { + const req = Object.fromEntries(sections.map(s => [s, {foo: 'bar'}])); + onlyOneClientSection(req); + expect(Object.keys(req)).to.eql([winner]); + }) + }); + it('should not choke if none of the sections are in the request', () => { + const req = {}; + onlyOneClientSection(req); + expect(req).to.eql({}); + }); +}); diff --git a/test/spec/ortbConverter/gdpr_spec.js b/test/spec/ortbConverter/gdpr_spec.js new file mode 100644 index 00000000000..78fd1830438 --- /dev/null +++ b/test/spec/ortbConverter/gdpr_spec.js @@ -0,0 +1,9 @@ +import {setOrtbAdditionalConsent} from '../../../modules/consentManagement.js'; + +describe('pbjs -> ortb addtlConsent', () => { + it('sets ConsentedProvidersSettings', () => { + const req = {}; + setOrtbAdditionalConsent(req, {gdprConsent: {addtlConsent: 'tl'}}); + expect(req.user.ext.ConsentedProvidersSettings.consented_providers).to.eql('tl'); + }); +}) diff --git a/test/spec/ortbConverter/mediaTypes_spec.js b/test/spec/ortbConverter/mediaTypes_spec.js new file mode 100644 index 00000000000..4f4fd99fb75 --- /dev/null +++ b/test/spec/ortbConverter/mediaTypes_spec.js @@ -0,0 +1,67 @@ +import {ORTB_MTYPES, setResponseMediaType} from '../../../libraries/ortbConverter/processors/mediaType.js'; +import {BANNER} from '../../../src/mediaTypes.js'; +import {extPrebidMediaType} from '../../../libraries/pbsExtensions/processors/mediaType.js'; + +function testMtype(processor) { + describe('respects 2.6 mtype', () => { + Object.entries(ORTB_MTYPES).forEach(([mtype, pbtype]) => { + it(`${mtype} -> ${pbtype}`, () => { + const resp = {}; + processor(resp, { + mtype: parseInt(mtype, 10) + }, {}); + expect(resp.mediaType).to.eql(pbtype); + }); + }); + }); +} + +describe('ortb -> pbjs mediaType conversion', () => { + testMtype(setResponseMediaType); + it('throws if mtype is missing', () => { + expect(() => { + setResponseMediaType({}, {}); + }).to.throw(); + }); + + it('respects pre-set bidResponse.mediaType', () => { + const resp = {mediaType: 'video'}; + setResponseMediaType(resp, {mtype: 1}); + expect(resp.mediaType).to.eql('video'); + }); + + it('gives precedence to context.mediaType', () => { + const resp = {}; + setResponseMediaType(resp, {mtype: 1}, {mediaType: 'video'}); + expect(resp.mediaType).to.eql('video') + }) +}); + +describe('ortb -> pbjs mediaType conversion based on ext.prebid.type', () => { + testMtype(extPrebidMediaType); + describe('respects ext.prebid.type', () => { + Object.values(ORTB_MTYPES).forEach(mediaType => { + const response = {}; + extPrebidMediaType(response, { + ext: { + prebid: { + type: mediaType + } + } + }, {}); + expect(response.mediaType).to.eql(mediaType); + }); + }); + + it('defaults to banner', () => { + const response = {}; + extPrebidMediaType(response, {}, {}); + expect(response.mediaType).to.eql(BANNER); + }); + + it('gives precedence to context.mediaType', () => { + const response = {}; + extPrebidMediaType(response, {ext: {prebid: {type: 'banner'}}}, {mediaType: 'video'}); + expect(response.mediaType).to.eql('video'); + }) +}); diff --git a/test/spec/ortbConverter/mergeProcessors_spec.js b/test/spec/ortbConverter/mergeProcessors_spec.js new file mode 100644 index 00000000000..ba15de06031 --- /dev/null +++ b/test/spec/ortbConverter/mergeProcessors_spec.js @@ -0,0 +1,59 @@ +import {mergeProcessors} from '../../../libraries/ortbConverter/lib/mergeProcessors.js'; +import {BID_RESPONSE, IMP, REQUEST, RESPONSE} from '../../../src/pbjsORTB.js'; + +describe('mergeProcessors', () => { + it('can merge', () => { + const result = mergeProcessors({ + [REQUEST]: { + first: { + priority: 0, + fn: 'first' + } + }, + [RESPONSE]: { + second: { + priority: 1, + fn: 'second' + } + } + }, { + [REQUEST]: { + first: { + fn: 'overriden' + } + }, + [IMP]: { + third: { + fn: 'third' + } + } + }, { + [IMP]: { + third: { + priority: 3, + fn: 'overridden' + } + } + }); + expect(result).to.eql({ + [REQUEST]: { + first: { + fn: 'overriden' + } + }, + [IMP]: { + third: { + priority: 3, + fn: 'overridden' + }, + }, + [RESPONSE]: { + second: { + priority: 1, + fn: 'second' + } + }, + [BID_RESPONSE]: {} + }); + }); +}); diff --git a/test/spec/ortbConverter/multibid_spec.js b/test/spec/ortbConverter/multibid_spec.js new file mode 100644 index 00000000000..4886aaf6069 --- /dev/null +++ b/test/spec/ortbConverter/multibid_spec.js @@ -0,0 +1,35 @@ +import {config} from 'src/config.js'; +import {setOrtbExtPrebidMultibid} from '../../../modules/multibid/index.js'; + +describe('pbjs - ortb ext.prebid.multibid', () => { + before(() => { + config.resetConfig(); + }); + afterEach(() => { + config.resetConfig(); + }); + + it('sets ext.prebid.multibid according to config', () => { + config.setConfig({ + multibid: [ + { + bidder: 'A', + maxBids: 2 + }, + { + bidder: 'B', + maxBids: 3 + } + ] + }); + const req = {}; + setOrtbExtPrebidMultibid(req); + expect(req.ext.prebid.multibid).to.eql([{bidder: 'A', maxbids: 2}, {bidder: 'B', maxbids: 3}]); + }); + + it('does not set it if not configured', () => { + const req = {}; + setOrtbExtPrebidMultibid(req); + expect(req).to.eql({}); + }) +}); diff --git a/test/spec/ortbConverter/native_spec.js b/test/spec/ortbConverter/native_spec.js new file mode 100644 index 00000000000..56c733817cd --- /dev/null +++ b/test/spec/ortbConverter/native_spec.js @@ -0,0 +1,95 @@ +import {fillNativeImp, fillNativeResponse} from '../../../libraries/ortbConverter/processors/native.js'; +import {BANNER, NATIVE} from '../../../src/mediaTypes.js'; + +describe('pbjs -> ortb native requests', () => { + function toNative(bidRequest, context) { + const imp = {}; + fillNativeImp(imp, bidRequest, context); + return imp; + } + + it('should do nothing if request has no nativeOrtbRequest', () => { + expect(toNative({}, {})).to.eql({}); + }); + + it('should set imp.native according to nativeOrtbRequest', () => { + const nativeOrtbRequest = {ver: 'version', prop: 'value', assets: [{}]}; + const imp = toNative({nativeOrtbRequest}, {}); + expect(imp.native.ver).to.eql('version'); + expect(JSON.parse(imp.native.request)).to.eql(nativeOrtbRequest); + }); + + it('should do nothing if context.mediaType is set but is not NATIVE', () => { + expect(toNative({nativeOrtbRequest: {ver: 'version'}}, {mediaType: BANNER})).to.eql({}) + }); + + it('should merge context.nativeRequest', () => { + const nativeOrtbRequest = {ver: 'version', eventtrackers: [{tracker: 'req'}], assets: [{}]}; + const nativeDefaults = {eventtrackers: [{tracker: 'default'}], other: 'other'}; + const imp = toNative({nativeOrtbRequest}, {nativeRequest: nativeDefaults}); + expect(imp.native.ver).to.eql('version'); + expect(JSON.parse(imp.native.request)).to.eql({ + assets: [{}], + ver: 'version', + eventtrackers: [{tracker: 'req'}], + other: 'other' + }); + }); + + it('should keep ortb2Imp.native', () => { + const imp = { + native: { + something: 'orother' + } + } + fillNativeImp(imp, {nativeOrtbRequest: {ver: 'version'}}, {}); + expect(imp.native.something).to.eql('orother') + }); + + it('should do nothing if there are no assets', () => { + const imp = {}; + fillNativeImp(imp, {nativeOrtbRequest: {assets: []}}, {}); + expect(imp).to.eql({}); + }) +}); + +describe('ortb -> ortb native response', () => { + const MOCK_NATIVE_RESPONSE = { + property: 'value', + assets: [ + { + id: 0 + } + ] + } + Object.entries({ + 'serialized': JSON.stringify(MOCK_NATIVE_RESPONSE), + 'an object': MOCK_NATIVE_RESPONSE + }).forEach(([t, adm]) => { + describe(`when adm is ${t}`, () => { + let bid; + beforeEach(() => { + bid = {adm}; + }) + it('should set bidResponse.native', () => { + const bidResponse = { + mediaType: NATIVE + }; + fillNativeResponse(bidResponse, bid, {}); + expect(bidResponse.native).to.eql({ortb: MOCK_NATIVE_RESPONSE}); + }); + }); + it('should throw if response has no assets', () => { + expect(() => fillNativeResponse({mediaType: NATIVE}, {adm: {...MOCK_NATIVE_RESPONSE, assets: null}}, {})).to.throw; + }) + it('should do nothing if bidResponse.mediaType is not NATIVE', () => { + const bidResponse = { + mediaType: BANNER + }; + fillNativeResponse(bidResponse, {adm: MOCK_NATIVE_RESPONSE}, {}); + expect(bidResponse).to.eql({ + mediaType: BANNER + }) + }) + }) +}); diff --git a/test/spec/ortbConverter/pbjsORTB_spec.js b/test/spec/ortbConverter/pbjsORTB_spec.js new file mode 100644 index 00000000000..16aefec2b35 --- /dev/null +++ b/test/spec/ortbConverter/pbjsORTB_spec.js @@ -0,0 +1,67 @@ +import { + DEFAULT, + PBS, + PROCESSOR_TYPES, + processorRegistry, + REQUEST +} from '../../../src/pbjsORTB.js'; + +describe('pbjsORTB register / get processors', () => { + let registerOrtbProcessor, getProcessors; + beforeEach(() => { + ({registerOrtbProcessor, getProcessors} = processorRegistry()); + }) + PROCESSOR_TYPES.forEach(type => { + it(`can get and set ${type} processors`, () => { + const proc = function () {}; + registerOrtbProcessor({ + type, + name: 'test', + fn: proc + }); + expect(getProcessors(DEFAULT)).to.eql({ + [type]: { + test: { + priority: 0, + fn: proc + } + } + }); + }); + }); + + it('throws on wrong type', () => { + expect(() => registerOrtbProcessor({ + type: 'incorrect', + name: 'test', + fn: function () {} + })).to.throw(); + }); + + it('can set priority', () => { + const proc = function () {}; + registerOrtbProcessor({type: REQUEST, name: 'test', fn: proc, priority: 10}); + expect(getProcessors(DEFAULT)).to.eql({ + [REQUEST]: { + test: { + priority: 10, + fn: proc + } + } + }) + }); + + it('can assign processors to specific dialects', () => { + const proc = function () {}; + registerOrtbProcessor({type: REQUEST, name: 'test', fn: proc, dialects: [PBS]}); + expect(getProcessors(DEFAULT)).to.eql({}); + expect(getProcessors(PBS)).to.eql({ + [REQUEST]: { + test: { + priority: 0, + fn: proc + } + } + }) + }); +}); diff --git a/test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js b/test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js new file mode 100644 index 00000000000..e17f9e856b0 --- /dev/null +++ b/test/spec/ortbConverter/pbsExtensions/adUnitCode_spec.js @@ -0,0 +1,23 @@ +import {setImpAdUnitCode} from '../../../../libraries/pbsExtensions/processors/adUnitCode.js'; + +describe('pbjs -> ortb adunit code to imp[].ext.prebid.adunitcode', () => { + function setImp(bidRequest) { + const imp = {}; + setImpAdUnitCode(imp, bidRequest); + return imp; + } + + it('it sets adunitcode in ext.prebid.adunitcode when adUnitCode is present', () => { + expect(setImp({bidder: 'mockBidder', adUnitCode: 'mockAdUnit'})).to.eql({ + 'ext': { + 'prebid': { + 'adunitcode': 'mockAdUnit' + } + } + }) + }); + + it('does not set adunitcode in ext.prebid.adunitcode if adUnit is undefined', () => { + expect(setImp({bidder: 'mockBidder'})).to.eql({}); + }); +}); diff --git a/test/spec/ortbConverter/pbsExtensions/aliases_spec.js b/test/spec/ortbConverter/pbsExtensions/aliases_spec.js new file mode 100644 index 00000000000..712ceaa397c --- /dev/null +++ b/test/spec/ortbConverter/pbsExtensions/aliases_spec.js @@ -0,0 +1,57 @@ +import {setRequestExtPrebidAliases} from '../../../../libraries/pbsExtensions/processors/aliases.js'; + +describe('PBS - ortb ext.prebid.aliases', () => { + let aliasRegistry, bidderRegistry; + + function setAliases(bidderRequest) { + const req = {} + setRequestExtPrebidAliases(req, bidderRequest, {}, { + am: { + bidderRegistry, + aliasRegistry + } + }); + return req; + } + + beforeEach(() => { + aliasRegistry = {}; + bidderRegistry = {}; + }) + + describe('has no effect if', () => { + it('bidder is not an alias', () => { + expect(setAliases({bidderCode: 'not-an-alias'})).to.eql({}); + }); + + it('bidder sets skipPbsAliasing', () => { + aliasRegistry['alias'] = 'bidder'; + bidderRegistry['alias'] = { + getSpec() { + return { + skipPbsAliasing: true + } + } + }; + expect(setAliases({bidderCode: 'alias'})).to.eql({}); + }); + }); + + it('sets ext.prebid.aliases.BIDDER', () => { + aliasRegistry['alias'] = 'bidder'; + bidderRegistry['alias'] = { + getSpec() { + return {} + } + }; + expect(setAliases({bidderCode: 'alias'})).to.eql({ + ext: { + prebid: { + aliases: { + alias: 'bidder' + } + } + } + }) + }); +}) diff --git a/test/spec/ortbConverter/pbsExtensions/params_spec.js b/test/spec/ortbConverter/pbsExtensions/params_spec.js new file mode 100644 index 00000000000..73b92a0755d --- /dev/null +++ b/test/spec/ortbConverter/pbsExtensions/params_spec.js @@ -0,0 +1,96 @@ +import {setImpBidParams} from '../../../../libraries/pbsExtensions/processors/params.js'; + +describe('pbjs -> ortb bid params to imp[].ext.prebid.BIDDER', () => { + let bidderRegistry, index, adUnit; + beforeEach(() => { + bidderRegistry = {}; + adUnit = {code: 'mockAdUnit'}; + index = { + getAdUnit() { + return adUnit; + } + } + }); + + function setParams(bidRequest, context, deps = {}) { + const imp = {}; + setImpBidParams(imp, bidRequest, context, Object.assign({bidderRegistry, index}, deps)) + return imp; + } + + it('sets params in ext.prebid.bidder.BIDDER', () => { + expect(setParams({bidder: 'mockBidder', params: {a: 'param'}})).to.eql({ + ext: { + prebid: { + bidder: { + mockBidder: { + a: 'param' + } + } + } + } + }) + }); + + it('has no effect if bidRequest has no params', () => { + expect(setParams({bidder: 'mockBidder'})).to.eql({}); + }) + + describe('when adapter provides transformBidParams', () => { + let transform, bidderRequest; + beforeEach(() => { + bidderRequest = {bidderCode: 'mockBidder'}; + transform = sinon.stub().callsFake((p) => Object.assign({transformed: true}, p)); + bidderRegistry.mockBidder = { + getSpec() { + return { + transformBidParams: transform + } + } + } + }) + + it('runs params through transform', () => { + expect(setParams({bidder: 'mockBidder', params: {a: 'param'}}, {bidderRequest})).to.eql({ + ext: { + prebid: { + bidder: { + mockBidder: { + a: 'param', + transformed: true + } + } + } + } + }); + }); + + it('runs through transform even if bid has no params', () => { + expect(setParams({bidder: 'mockBidder'}, {bidderRequest})).to.eql({ + ext: { + prebid: { + bidder: { + mockBidder: { + transformed: true + } + } + } + } + }) + }) + + it('by default, passes adUnit from index, bidderRequest from context', () => { + const params = {a: 'param'}; + setParams({bidder: 'mockBidder', params}, {bidderRequest}); + sinon.assert.calledWith(transform, params, true, adUnit, [bidderRequest]) + }); + + it('uses provided adUnit, bidderRequests', () => { + const adUnit = {code: 'other-ad-unit'}; + const bidderRequests = [{bidderCode: 'one'}, {bidderCode: 'two'}]; + const params = {a: 'param'}; + setParams({bidder: 'mockBidder', params}, {}, {adUnit, bidderRequests}); + sinon.assert.calledWith(transform, params, true, adUnit, bidderRequests); + }) + }); +}); diff --git a/test/spec/ortbConverter/pbsExtensions/video_spec.js b/test/spec/ortbConverter/pbsExtensions/video_spec.js new file mode 100644 index 00000000000..5bba32c447a --- /dev/null +++ b/test/spec/ortbConverter/pbsExtensions/video_spec.js @@ -0,0 +1,52 @@ +import {setBidResponseVideoCache} from '../../../../libraries/pbsExtensions/processors/video.js'; + +describe('pbjs - ortb videoCacheKey based on ext.prebid', () => { + const EXT_PREBID_CACHE = { + ext: { + prebid: { + cache: { + vastXml: { + cacheId: 'id', + url: 'url' + } + } + } + } + } + + function setCache(bid) { + const bidResponse = {mediaType: 'video'}; + setBidResponseVideoCache(bidResponse, bid); + return bidResponse; + } + + it('has no effect if mediaType is not video', () => { + const resp = {mediaType: 'banner'}; + setBidResponseVideoCache(resp, EXT_PREBID_CACHE); + expect(resp).to.eql({mediaType: 'banner'}); + }); + + it('sets videoCacheKey, vastUrl from ext.prebid.cache.vastXml', () => { + sinon.assert.match(setCache(EXT_PREBID_CACHE), { + videoCacheKey: 'id', + vastUrl: 'url' + }); + }); + + it('sets videoCacheKey, vastUrl from ext.prebid.targeting', () => { + sinon.assert.match(setCache({ + ext: { + prebid: { + targeting: { + hb_uuid: 'id', + hb_cache_host: 'host', + hb_cache_path: '/path' + } + } + } + }), { + vastUrl: 'https://host/path?uuid=id', + videoCacheKey: 'id' + }) + }); +}); diff --git a/test/spec/ortbConverter/priceFloors_spec.js b/test/spec/ortbConverter/priceFloors_spec.js new file mode 100644 index 00000000000..f6d37992711 --- /dev/null +++ b/test/spec/ortbConverter/priceFloors_spec.js @@ -0,0 +1,143 @@ +import {config} from 'src/config.js'; +import {setOrtbExtPrebidFloors, setOrtbImpBidFloor} from '../../../modules/priceFloors.js'; +import 'src/prebid.js'; + +describe('pbjs - ortb imp floor params', () => { + before(() => { + config.resetConfig(); + }); + + afterEach(() => { + config.resetConfig(); + }); + + Object.entries({ + 'has no getFloor': {}, + 'has getFloor that throws': { + getFloor: sinon.stub().callsFake(() => { throw new Error() }), + }, + 'returns invalid floor': { + getFloor: sinon.stub().callsFake(() => ({floor: NaN, currency: null})) + } + }).forEach(([t, req]) => { + it(`has no effect if bid ${t}`, () => { + const imp = {}; + setOrtbImpBidFloor(imp, {}, {}); + expect(imp).to.eql({}); + }) + }) + + it('sets bidfoor and bidfloorcur according to getFloor', () => { + const imp = {}; + const req = { + getFloor() { + return { + currency: 'EUR', + floor: '1.23' + } + } + }; + setOrtbImpBidFloor(imp, req, {}); + expect(imp).to.eql({ + bidfloor: 1.23, + bidfloorcur: 'EUR' + }) + }); + + Object.entries({ + 'missing currency': {floor: 1.23}, + 'missing floor': {currency: 'USD'}, + 'not a number': {floor: 'abc', currency: 'USD'} + }).forEach(([t, floor]) => { + it(`should not set bidfloor if floor is ${t}`, () => { + const imp = {}; + const req = { + getFloor: () => floor + } + setOrtbImpBidFloor(imp, req, {}); + expect(imp).to.eql({}); + }) + }) + + describe('asks for floor in currency', () => { + let req; + beforeEach(() => { + req = { + getFloor(opts) { + return { + floor: 1.23, + currency: opts.currency + } + } + } + }) + + it('from context.currency', () => { + const imp = {}; + setOrtbImpBidFloor(imp, req, {currency: 'JPY'}); + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }) + expect(imp.bidfloorcur).to.eql('JPY'); + }); + + it('from config', () => { + const imp = {}; + config.setConfig({ + currency: { + adServerCurrency: 'EUR' + } + }); + setOrtbImpBidFloor(imp, req, {}); + expect(imp.bidfloorcur).to.eql('EUR'); + }); + + it('defaults to USD', () => { + const imp = {}; + setOrtbImpBidFloor(imp, req, {}); + expect(imp.bidfloorcur).to.eql('USD'); + }) + }); + + it('asks for specific mediaType if context.mediaType is set', () => { + let reqMediaType; + const req = { + getFloor(opts) { + reqMediaType = opts.mediaType; + } + } + setOrtbImpBidFloor({}, req, {mediaType: 'banner'}); + expect(reqMediaType).to.eql('banner'); + }) +}); + +describe('setOrtbExtPrebidFloors', () => { + before(() => { + config.setConfig({floors: {}}); + }) + after(() => { + config.setConfig({floors: {enabled: false}}); + }); + + it('should set ext.prebid.floors.enabled to false', () => { + const req = {}; + setOrtbExtPrebidFloors(req); + expect(req.ext.prebid.floors.enabled).to.equal(false); + }) + + it('should respect fpd', () => { + const req = { + ext: { + prebid: { + floors: { + enabled: true + } + } + } + } + setOrtbExtPrebidFloors(req); + expect(req.ext.prebid.floors.enabled).to.equal(true); + }) +}) diff --git a/test/spec/ortbConverter/schain_spec.js b/test/spec/ortbConverter/schain_spec.js new file mode 100644 index 00000000000..8eeef445948 --- /dev/null +++ b/test/spec/ortbConverter/schain_spec.js @@ -0,0 +1,33 @@ +import {setOrtbSourceExtSchain} from '../../../modules/schain.js'; + +describe('pbjs - ortb source.ext.schain', () => { + it('sets schain from request', () => { + const req = {}; + setOrtbSourceExtSchain(req, {}, { + bidRequests: [{schain: {s: 'chain'}}] + }); + expect(req.source.ext.schain).to.eql({s: 'chain'}); + }); + + it('does not set it if missing', () => { + const req = {}; + setOrtbSourceExtSchain(req, {}, {bidRequests: [{}]}); + expect(req).to.eql({}); + }) + + it('does not set it if already in request', () => { + const req = { + source: { + ext: { + schain: {s: 'chain'} + } + } + } + setOrtbSourceExtSchain(req, {}, { + bidRequests: [{ + schain: {other: 'chain'} + }] + }); + expect(req.source.ext.schain).to.eql({s: 'chain'}); + }) +}); diff --git a/test/spec/ortbConverter/userId_spec.js b/test/spec/ortbConverter/userId_spec.js new file mode 100644 index 00000000000..0b1c8878edf --- /dev/null +++ b/test/spec/ortbConverter/userId_spec.js @@ -0,0 +1,21 @@ +import {setOrtbUserExtEids} from '../../../modules/userId/index.js'; + +describe('pbjs - ortb user eids', () => { + it('sets user.ext.eids from request', () => { + const req = {}; + setOrtbUserExtEids(req, {}, { + bidRequests: [ + { + userIdAsEids: {e: 'id'} + } + ] + }); + expect(req.user.ext.eids).to.eql({e: 'id'}); + }); + + it('has no effect if requests have no eids', () => { + const req = {}; + setOrtbUserExtEids(req, {}, [{}]); + expect(req).to.eql({}); + }) +}) diff --git a/test/spec/ortbConverter/video_spec.js b/test/spec/ortbConverter/video_spec.js new file mode 100644 index 00000000000..8ac6d8b4d08 --- /dev/null +++ b/test/spec/ortbConverter/video_spec.js @@ -0,0 +1,189 @@ +import {fillVideoImp, fillVideoResponse, VALIDATIONS} from '../../../libraries/ortbConverter/processors/video.js'; +import {BANNER, VIDEO} from '../../../src/mediaTypes.js'; + +describe('pbjs -> ortb video conversion', () => { + [ + { + t: 'non-video request', + request: { + mediaTypes: { + banner: {} + } + }, + imp: {} + }, + { + t: 'instream video request', + request: { + mediaTypes: { + video: { + playerSize: [[1, 2]], + context: 'instream', + mimes: ['video/mp4'], + skip: 1, + } + } + }, + imp: { + video: { + w: 1, + h: 2, + mimes: ['video/mp4'], + skip: 1, + placement: 1, + }, + }, + }, + { + t: 'outstream video request', + request: { + mediaTypes: { + video: { + playerSize: [[1, 2]], + context: 'outstream', + mimes: ['video/mp4'], + skip: 1 + } + } + }, + imp: { + video: { + w: 1, + h: 2, + mimes: ['video/mp4'], + skip: 1, + }, + }, + }, + { + t: 'video request with explicit placement', + request: { + mediaTypes: { + video: { + playerSize: [[1, 2]], + placement: 'explicit' + } + } + }, + imp: { + video: { + w: 1, + h: 2, + placement: 'explicit', + } + } + }, + { + t: 'video request with multiple playerSizes', + request: { + mediaTypes: { + video: { + playerSize: [[1, 2], [3, 4]] + } + } + }, + imp: { + video: { + w: 1, + h: 2, + } + } + }, + { + t: 'video request with 2-tuple playerSize', + request: { + mediaTypes: { + video: { + playerSize: [1, 2] + } + } + }, + imp: { + video: { + w: 1, + h: 2, + } + } + }, + ].forEach(({t, request, imp}) => { + it(`can handle ${t}`, () => { + const actual = {}; + fillVideoImp(actual, request, {}); + expect(actual).to.eql(imp); + }); + }); + + it('should keep ortb2Imp.video', () => { + const imp = { + video: { + someParam: 'someValue' + } + }; + fillVideoImp(imp, {mediaTypes: {video: {playerSize: [[1, 2]]}}}, {}); + expect(imp.video.someParam).to.eql('someValue'); + }); + + it('does nothing is context.mediaType is set but is not VIDEO', () => { + const imp = {}; + fillVideoImp(imp, {mediaTypes: {video: {playerSize: [[1, 2]]}}}, {mediaType: BANNER}); + expect(imp).to.eql({}); + }); +}); + +describe('ortb -> pbjs video conversion', () => { + [ + { + t: 'non-video response', + seatbid: {}, + response: { + mediaType: BANNER + }, + expected: { + mediaType: BANNER + } + }, + { + t: 'simple video response', + seatbid: { + adm: 'mockAdm', + nurl: 'mockNurl' + }, + response: { + mediaType: VIDEO, + }, + context: { + imp: { + video: { + w: 1, + h: 2 + } + } + }, + expected: { + mediaType: VIDEO, + playerWidth: 1, + playerHeight: 2, + vastXml: 'mockAdm', + vastUrl: 'mockNurl' + } + }, + { + t: 'video response without playerSize', + seatbid: {}, + response: { + mediaType: VIDEO, + }, + context: { + imp: {} + }, + expected: { + mediaType: VIDEO + } + } + ].forEach(({t, seatbid, context, response, expected}) => { + it(`can handle ${t}`, () => { + fillVideoResponse(response, seatbid, context); + expect(response).to.eql(expected); + }) + }) +}) diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js index 3f33d1646a1..a0ffc3eddbe 100644 --- a/test/spec/refererDetection_spec.js +++ b/test/spec/refererDetection_spec.js @@ -2,94 +2,7 @@ import {detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.j import {config} from 'src/config.js'; import {expect} from 'chai'; -/** - * Build a walkable linked list of window-like objects for testing. - * - * @param {Array} urls Array of URL strings starting from the top window. - * @param {string} [topReferrer] - * @param {string} [canonicalUrl] - * @param {boolean} [ancestorOrigins] - * @returns {Object} - */ -function buildWindowTree(urls, topReferrer = null, canonicalUrl = null, ancestorOrigins = false) { - /** - * Find the origin from a given fully-qualified URL. - * - * @param {string} url The fully qualified URL - * @returns {string|null} - */ - function getOrigin(url) { - const originRegex = new RegExp('^(https?://[^/]+/?)'); - - const result = originRegex.exec(url); - - if (result && result[0]) { - return result[0]; - } - - return null; - } - - const inaccessibles = []; - - let previousWindow, topWindow; - const topOrigin = getOrigin(urls[0]); - - const windowList = urls.map((url, index) => { - const thisOrigin = getOrigin(url), - sameOriginAsPrevious = index === 0 ? true : (getOrigin(urls[index - 1]) === thisOrigin), - sameOriginAsTop = thisOrigin === topOrigin; - - const win = { - location: { - href: url, - }, - document: { - referrer: index === 0 ? topReferrer : urls[index - 1] - } - }; - - if (topWindow == null) { - topWindow = win; - win.document.querySelector = function (selector) { - if (selector === 'link[rel=\'canonical\']') { - return { - href: canonicalUrl - }; - } - return null; - }; - } - - if (sameOriginAsPrevious) { - win.parent = previousWindow; - } else { - win.parent = inaccessibles[inaccessibles.length - 1]; - } - if (ancestorOrigins) { - win.location.ancestorOrigins = urls.slice(0, index).reverse().map(getOrigin); - } - win.top = sameOriginAsTop ? topWindow : inaccessibles[0]; - - const inWin = {parent: inaccessibles[inaccessibles.length - 1], top: inaccessibles[0]}; - if (index === 0) { - inWin.top = inWin; - } - ['document', 'location'].forEach((prop) => { - Object.defineProperty(inWin, prop, { - get: function () { - throw new Error('cross-origin access'); - } - }); - }); - inaccessibles.push(inWin); - previousWindow = win; - - return win; - }); - - return windowList[windowList.length - 1]; -} +import { buildWindowTree } from '../helpers/refererDetectionHelper'; describe('Referer detection', () => { afterEach(function () { @@ -128,11 +41,49 @@ describe('Referer detection', () => { numIframes: 0, stack: ['https://example.com/some/page'], canonicalUrl: 'https://example.com/canonical/page', - page: 'https://example.com/canonical/page', + page: 'https://example.com/some/page', ref: 'https://othersite.com/', domain: 'example.com' }); }); + + it('Should set page and canonical to pageUrl value set in config if present, even if canonical url is also present in head', () => { + config.setConfig({'pageUrl': 'https://www.set-from-config.com/path'}); + const testWindow = buildWindowTree(['https://example.com/some/page'], 'https://othersite.com/', 'https://example.com/canonical/page'), + result = detectReferer(testWindow)(); + + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page', + location: 'https://example.com/some/page', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['https://example.com/some/page'], + canonicalUrl: 'https://www.set-from-config.com/path', + page: 'https://www.set-from-config.com/path', + ref: 'https://othersite.com/', + domain: 'www.set-from-config.com' + }); + }); + + it('Should set page with query params if canonical url is present without query params but the current page does have them', () => { + config.setConfig({'pageUrl': 'https://www.set-from-config.com/path'}); + const testWindow = buildWindowTree(['https://example.com/some/page?query1=123&query2=456'], 'https://othersite.com/', 'https://example.com/canonical/page'), + result = detectReferer(testWindow)(); + + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page?query1=123&query2=456', + location: 'https://example.com/some/page?query1=123&query2=456', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['https://example.com/some/page?query1=123&query2=456'], + canonicalUrl: 'https://www.set-from-config.com/path', + page: 'https://www.set-from-config.com/path?query1=123&query2=456', + ref: 'https://othersite.com/', + domain: 'www.set-from-config.com' + }); + }); }); describe('Friendly iframes', () => { @@ -174,7 +125,7 @@ describe('Referer detection', () => { 'https://example.com/third/page' ], canonicalUrl: 'https://example.com/canonical/page', - page: 'https://example.com/canonical/page', + page: 'https://example.com/some/page', ref: 'https://othersite.com/', domain: 'example.com' }); @@ -317,7 +268,7 @@ describe('Referer detection', () => { 'https://ad-iframe.ampproject.org/ad' ], canonicalUrl: 'https://example.com/some/page/', - page: 'https://example.com/some/page/', + page: 'https://example.com/some/page/amp/', ref: null, domain: 'example.com' }); @@ -344,7 +295,7 @@ describe('Referer detection', () => { 'https://ad-iframe.ampproject.org/ad' ], canonicalUrl: 'https://example.com/some/page/', - page: 'https://example.com/some/page/', + page: 'https://example.com/some/page/amp/', ref: null, domain: 'example.com' }); @@ -384,7 +335,7 @@ describe('Referer detection', () => { 'https://ad-iframe.ampproject.org/ad' ], canonicalUrl: 'https://example.com/some/page/', - page: 'https://example.com/some/page/', + page: 'https://example.com/some/page/amp/', ref: null, domain: 'example.com', }); @@ -412,7 +363,7 @@ describe('Referer detection', () => { 'https://ad-iframe.ampproject.org/ad' ], canonicalUrl: 'https://example.com/some/page/', - page: 'https://example.com/some/page/', + page: 'https://example.com/some/page/amp/', ref: null, domain: 'example.com' }); @@ -441,7 +392,7 @@ describe('Referer detection', () => { 'https://ad-iframe.ampproject.org/ad' ], canonicalUrl: 'https://example.com/some/page/', - page: 'https://example.com/some/page/', + page: 'https://example.com/some/page/amp/', ref: null, domain: 'example.com', }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 6e37da4e85f..06366e8cc5c 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -20,6 +20,7 @@ import { setSizeConfig } from 'src/sizeMapping.js'; import {find, includes} from 'src/polyfill.js'; import s2sTesting from 'modules/s2sTesting.js'; import {hook} from '../../../../src/hook.js'; +import {auctionManager} from '../../../../src/auctionManager.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -1756,6 +1757,153 @@ describe('adapterManager tests', function () { requests.appnexus.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.appnexus.ortb2)); }); + it('should merge in bid-level ortb2Imp with adUnit-level ortb2Imp', () => { + const adUnit = { + ...adUnits[1], + ortb2Imp: {oneone: {twoone: 'val'}, onetwo: 'val'} + }; + adUnit.bids[0].ortb2Imp = {oneone: {twotwo: 'val'}, onethree: 'val', onetwo: 'val2'}; + const reqs = Object.fromEntries( + adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}) + .map((req) => [req.bidderCode, req]) + ); + sinon.assert.match(reqs[adUnit.bids[0].bidder].bids[0].ortb2Imp, { + oneone: { + twoone: 'val', + twotwo: 'val', + }, + onetwo: 'val2', + onethree: 'val' + }) + sinon.assert.match(reqs[adUnit.bids[1].bidder].bids[0].ortb2Imp, adUnit.ortb2Imp) + }) + + it('picks ortb2Imp from "module" when only one s2sConfig is set', () => { + config.setConfig({ + s2sConfig: [ + { + enabled: true, + adapter: 'mockS2S1', + } + ] + }); + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + ortb2Imp: { + p2: 'module' + } + } + ] + }; + const req = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {})[0]; + [req.adUnitsS2SCopy[0].ortb2Imp, req.bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'module' + }); + }); + }); + + describe('with named s2s configs', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: [ + { + enabled: true, + adapter: 'mockS2S1', + configName: 'one', + bidders: ['A'] + }, + { + enabled: true, + adapter: 'mockS2S2', + configName: 'two', + bidders: ['B'] + } + ] + }) + }); + + it('generates requests for "module" bids', () => { + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + params: {configName: 'one'}, + ortb2Imp: { + p2: 'one' + } + }, + { + module: 'pbsBidAdapter', + params: {configName: 'two'}, + ortb2Imp: { + p2: 'two' + } + } + ] + }; + const reqs = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}); + [reqs[0].adUnitsS2SCopy[0].ortb2Imp, reqs[0].bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'one' + }) + }); + [reqs[1].adUnitsS2SCopy[0].ortb2Imp, reqs[1].bids[0].ortb2Imp].forEach(imp => { + sinon.assert.match(imp, { + p1: 'adUnit', + p2: 'two' + }) + }); + }); + + it('applies module-level ortb2Imp to "normal" s2s requests', () => { + const adUnit = { + code: 'mockau', + ortb2Imp: { + p1: 'adUnit' + }, + bids: [ + { + module: 'pbsBidAdapter', + params: {configName: 'one'}, + ortb2Imp: { + p2: 'one' + } + }, + { + bidder: 'A', + ortb2Imp: { + p3: 'bidderA' + } + } + ] + }; + const reqs = adapterManager.makeBidRequests([adUnit], 123, 'auction-id', 123, [], {}); + expect(reqs.length).to.equal(1); + sinon.assert.match(reqs[0].adUnitsS2SCopy[0].ortb2Imp, { + p1: 'adUnit', + p2: 'one' + }) + sinon.assert.match(reqs[0].bids[0].ortb2Imp, { + p1: 'adUnit', + p2: 'one', + p3: 'bidderA' + }) + }); + }); + describe('when calling the s2s adapter', () => { beforeEach(() => { config.setConfig({ @@ -2543,4 +2691,105 @@ describe('adapterManager tests', function () { }) }); }); + + describe('callDataDeletionRequest', () => { + function delMethodForBidder(bidderCode) { + const del = sinon.stub(); + adapterManager.registerBidAdapter({ + callBids: sinon.stub(), + getSpec() { + return { + onDataDeletionRequest: del + } + } + }, bidderCode); + return del; + } + + function delMethodForAnalytics(provider) { + const del = sinon.stub(); + adapterManager.registerAnalyticsAdapter({ + code: provider, + adapter: { + enableAnalytics: sinon.stub(), + onDataDeletionRequest: del, + }, + }) + return del; + } + + Object.entries({ + 'bid adapters': delMethodForBidder, + 'analytics adapters': delMethodForAnalytics + }).forEach(([t, getDelMethod]) => { + describe(t, () => { + it('invokes onDataDeletionRequest', () => { + const del = getDelMethod('mockAdapter'); + adapterManager.callDataDeletionRequest(); + sinon.assert.calledOnce(del); + }); + + it('does not choke if onDeletionRequest throws', () => { + const del1 = getDelMethod('mockAdapter1'); + const del2 = getDelMethod('mockAdapter2'); + del1.throws(new Error()); + adapterManager.callDataDeletionRequest(); + sinon.assert.calledOnce(del1); + sinon.assert.calledOnce(del2); + }); + }) + }) + + describe('for bid adapters', () => { + let bidderRequests; + + beforeEach(() => { + bidderRequests = []; + sinon.stub(auctionManager, 'getBidsRequested').callsFake(() => bidderRequests); + }) + afterEach(() => { + auctionManager.getBidsRequested.restore(); + }) + + it('does not invoke onDataDeletionRequest on aliases', () => { + const del = delMethodForBidder('mockBidder'); + adapterManager.aliasBidAdapter('mockBidder', 'mockBidderAlias'); + adapterManager.aliasBidAdapter('mockBidderAlias2', 'mockBidderAlias'); + adapterManager.callDataDeletionRequest(); + sinon.assert.calledOnce(del); + }); + + it('passes known bidder requests', () => { + const del1 = delMethodForBidder('mockBidder1'); + const del2 = delMethodForBidder('mockBidder2'); + adapterManager.aliasBidAdapter('mockBidder1', 'mockBidder1Alias'); + adapterManager.aliasBidAdapter('mockBidder1Alias', 'mockBidder1Alias2') + bidderRequests = [ + { + bidderCode: 'mockBidder1', + id: 0 + }, + { + bidderCode: 'mockBidder2', + id: 1, + }, + { + bidderCode: 'mockBidder1Alias', + id: 2, + }, + { + bidderCode: 'someOtherBidder', + id: 3 + }, + { + bidderCode: 'mockBidder1Alias2', + id: 4 + } + ]; + adapterManager.callDataDeletionRequest(); + sinon.assert.calledWith(del1, [bidderRequests[0], bidderRequests[2], bidderRequests[4]]); + sinon.assert.calledWith(del2, [bidderRequests[1]]); + }) + }) + }); }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 7164e468e71..1eecfac47a7 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,4 +1,4 @@ -import { newBidder, registerBidder, preloadBidderMappingFile, storage } from 'src/adapters/bidderFactory.js'; +import {newBidder, registerBidder, preloadBidderMappingFile, storage, isValid} from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; import { expect } from 'chai'; @@ -62,6 +62,7 @@ describe('bidders created by newBidder', function () { }; addBidResponseStub = sinon.stub(); + addBidResponseStub.reject = sinon.stub(); doneStub = sinon.stub(); }); @@ -508,7 +509,7 @@ describe('bidders created by newBidder', function () { expect(userSyncStub.firstCall.args[2]).to.equal('usersync.com'); }); - it('should logError when required bid response params are missing', function () { + it('should logError and reject bid when required bid response params are missing', function () { const bidder = newBidder(spec); const bid = { @@ -532,9 +533,10 @@ describe('bidders created by newBidder', function () { bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(logErrorSpy.calledOnce).to.equal(true); + expect(addBidResponseStub.reject.calledOnce).to.be.true; }); - it('should logError when required bid response params are undefined', function () { + it('should logError and reject bid when required response params are undefined', function () { const bidder = newBidder(spec); const bid = { @@ -562,6 +564,7 @@ describe('bidders created by newBidder', function () { bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(logErrorSpy.calledOnce).to.equal(true); + expect(addBidResponseStub.reject.calledOnce).to.be.true; }); it('should require requestId from interpretResponse', () => { @@ -586,6 +589,7 @@ describe('bidders created by newBidder', function () { bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.called).to.be.false; + expect(addBidResponseStub.reject.calledOnce).to.be.true; }); }); @@ -856,6 +860,7 @@ describe('validate bid response: ', function () { }); addBidResponseStub = sinon.stub(); + addBidResponseStub.reject = sinon.stub(); doneStub = sinon.stub(); ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { const fakeResponse = sinon.stub(); @@ -954,7 +959,8 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; expect(logErrorSpy.calledWithMatch('Ignoring bid: Native bid missing some required properties.')).to.equal(true); }); } @@ -1074,7 +1080,8 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; expect(logWarnSpy.callCount).to.equal(1); }); @@ -1085,7 +1092,8 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; }); it('should log warning when the particular bidder is not specified in allowedAlternateBidderCodes and allowAlternateBidderCodes flag is true', function () { @@ -1096,7 +1104,8 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; expect(logWarnSpy.callCount).to.equal(1); }); @@ -1147,7 +1156,8 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; expect(logWarnSpy.callCount).to.equal(1); }); @@ -1159,7 +1169,7 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.called).to.equal(true); expect(logWarnSpy.callCount).to.equal(0); expect(logErrorSpy.callCount).to.equal(0); }); @@ -1172,8 +1182,9 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(addBidResponseStub.called).to.equal(false); expect(logWarnSpy.callCount).to.equal(1); + expect(addBidResponseStub.reject.calledOnce).to.be.true; }); it('should not accept the bid, when bidderSetting is missing for the bidder. It should fallback to standard setting and reject the bid', function () { @@ -1183,7 +1194,8 @@ describe('validate bid response: ', function () { spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; expect(logWarnSpy.callCount).to.equal(1); }); }); @@ -1385,3 +1397,42 @@ describe('preload mapping url hook', function() { clock.restore(); }); }); + +describe('bid response isValid', () => { + describe('size check', () => { + let req, index; + + beforeEach(() => { + req = { + ...MOCK_BIDS_REQUEST.bids[0], + mediaTypes: { + banner: { + sizes: [[1, 2], [3, 4]] + } + } + } + }); + + function mkResponse(width, height) { + return { + requestId: req.bidId, + width, + height, + cpm: 1, + ttl: 60, + creativeId: '123', + netRevenue: true, + currency: 'USD', + mediaType: 'banner', + } + } + + function checkValid(bid) { + return isValid('au', bid, {index: stubAuctionIndex({bidRequests: [req]})}); + } + + it('should succeed when response has a size that was in request', () => { + expect(checkValid(mkResponse(3, 4))).to.be.true; + }); + }) +}); diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index cb38aed9e47..58bf8c9eb25 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -8,6 +8,7 @@ import { import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import {hook} from '../../../../src/hook.js'; +import {VENDORLESS_GVLID} from '../../../../src/consentHandler.js'; describe('storage manager', function() { before(() => { @@ -73,7 +74,7 @@ describe('storage manager', function() { it('should respect (vendorless) consent enforcement', () => { storage.localStorageIsEnabled(); - expect(validateHook.args[0][1]).to.eql(true); // isVendorless should be set to true + expect(validateHook.args[0][1]).to.equal(VENDORLESS_GVLID); // gvlid should be set to VENDORLESS_GVLID }); it('should respect the deviceAccess flag', () => { diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 4585ddbfaaf..f9fe2e398b0 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -6,8 +6,14 @@ import CONSTANTS from 'src/constants.json'; import { auctionManager } from 'src/auctionManager.js'; import * as utils from 'src/utils.js'; import {deepClone} from 'src/utils.js'; +import {createBid} from '../../../../src/bidfactory.js'; +import {hook} from '../../../../src/hook.js'; -const bid1 = { +function mkBid(bid, status = CONSTANTS.STATUS.GOOD) { + return Object.assign(createBid(status), bid); +} + +const sampleBid = { 'bidderCode': 'rubicon', 'width': '300', 'height': '250', @@ -39,7 +45,9 @@ const bid1 = { 'ttl': 300 }; -const bid2 = { +const bid1 = mkBid(sampleBid); + +const bid2 = mkBid({ 'bidderCode': 'rubicon', 'width': '300', 'height': '250', @@ -67,9 +75,9 @@ const bid2 = { 'netRevenue': true, 'currency': 'USD', 'ttl': 300 -}; +}); -const bid3 = { +const bid3 = mkBid({ 'bidderCode': 'rubicon', 'width': '300', 'height': '600', @@ -97,9 +105,9 @@ const bid3 = { 'netRevenue': true, 'currency': 'USD', 'ttl': 300 -}; +}); -const nativeBid1 = { +const nativeBid1 = mkBid({ 'bidderCode': 'appnexus', 'width': 0, 'height': 0, @@ -165,8 +173,9 @@ const nativeBid1 = { [CONSTANTS.NATIVE_KEYS.image]: 'http://vcdn.adnxs.com/p/creative-image/94/22/cd/0f/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', [CONSTANTS.NATIVE_KEYS.icon]: 'http://vcdn.adnxs.com/p/creative-image/bd/59/a6/c6/bd59a6c6-0851-411d-a16d-031475a51312.png' } -}; -const nativeBid2 = { +}); + +const nativeBid2 = mkBid({ 'bidderCode': 'dgads', 'width': 0, 'height': 0, @@ -222,7 +231,7 @@ const nativeBid2 = { [CONSTANTS.NATIVE_KEYS.sponsoredBy]: 'test.com', [CONSTANTS.NATIVE_KEYS.clickUrl]: 'http://prebid.org/' } -}; +}); describe('targeting tests', function () { let sandbox; @@ -231,6 +240,10 @@ describe('targeting tests', function () { let bidCacheFilterFunction; let undef; + before(() => { + hook.ready(); + }); + beforeEach(function() { sandbox = sinon.sandbox.create(); @@ -256,6 +269,40 @@ describe('targeting tests', function () { bidCacheFilterFunction = undef; }); + describe('isBidNotExpired', () => { + let clock; + beforeEach(() => { + clock = sandbox.useFakeTimers(0); + }); + + Object.entries({ + 'bid.ttlBuffer': (bid, ttlBuffer) => { + bid.ttlBuffer = ttlBuffer + }, + 'setConfig({ttlBuffer})': (_, ttlBuffer) => { + config.setConfig({ttlBuffer}) + }, + }).forEach(([t, setup]) => { + describe(`respects ${t}`, () => { + [0, 2].forEach(ttlBuffer => { + it(`when ttlBuffer is ${ttlBuffer}`, () => { + const bid = { + responseTimestamp: 0, + ttl: 10, + } + setup(bid, ttlBuffer); + + expect(filters.isBidNotExpired(bid)).to.be.true; + clock.tick((bid.ttl - ttlBuffer) * 1000 - 100); + expect(filters.isBidNotExpired(bid)).to.be.true; + clock.tick(101); + expect(filters.isBidNotExpired(bid)).to.be.false; + }); + }); + }); + }); + }); + describe('getAllTargeting', function () { let amBidsReceivedStub; let amGetAdUnitsStub; @@ -287,6 +334,12 @@ describe('targeting tests', function () { bidExpiryStub.restore(); }); + it('should filter out NO_BID bids', () => { + bidsReceived = [mkBid(sampleBid, CONSTANTS.STATUS.NO_BID)]; + const tg = targetingInstance.getAllTargeting(); + expect(tg[bidsReceived[0].adUnitCode]).to.eql({}); + }); + describe('when handling different adunit targeting value types', function () { const adUnitCode = '/123456/header-bid-tag-0'; const adServerTargeting = {}; diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 6757ef42094..0867cb0e399 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -21,7 +21,10 @@ import {hook} from '../../../src/hook.js'; import {reset as resetDebugging} from '../../../src/debugging.js'; import $$PREBID_GLOBAL$$ from 'src/prebid.js'; import {resetAuctionState} from 'src/auction.js'; - +import {stubAuctionIndex} from '../../helpers/indexStub.js'; +import {createBid} from '../../../src/bidfactory.js'; +import {enrichFPD} from '../../../src/fpd/enrichment.js'; +import {mockFpdEnrichments} from '../../helpers/fpd.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -194,21 +197,25 @@ window.apntag = { } describe('Unit: Prebid Module', function () { - let bidExpiryStub + let bidExpiryStub, sandbox; before(() => { hook.ready(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); resetDebugging(); + sinon.stub(filters, 'isActualBid').returns(true); // stub this out so that we can use vanilla objects as bids }); beforeEach(function () { + sandbox = sinon.sandbox.create(); + mockFpdEnrichments(sandbox); bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake(() => true); configObj.setConfig({ useBidCache: true }); resetAuctionState(); }); afterEach(function() { + sandbox.restore(); $$PREBID_GLOBAL$$.adUnits = []; bidExpiryStub.restore(); configObj.setConfig({ useBidCache: false }); @@ -216,6 +223,7 @@ describe('Unit: Prebid Module', function () { after(function() { auctionManager.clearAllAuctions(); + filters.isActualBid.restore(); }); describe('and global adUnits', () => { @@ -504,8 +512,8 @@ describe('Unit: Prebid Module', function () { 'client_initiated_ad_counting': true, 'rtb': { 'banner': { - 'width': 728, - 'height': 90, + 'width': 300, + 'height': 250, 'content': '' }, 'trackers': [{ @@ -1239,7 +1247,7 @@ describe('Unit: Prebid Module', function () { it('should require doc and id params', function () { $$PREBID_GLOBAL$$.renderAd(); - var error = 'Error trying to write ad Id :undefined to the page. Missing document or adId'; + var error = 'Error trying to write ad Id :undefined to the page. Missing adId'; assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); }); @@ -1456,7 +1464,7 @@ describe('Unit: Prebid Module', function () { describe('requestBids', function () { let logMessageSpy; - let makeRequestsStub; + let makeRequestsStub, createAuctionStub; let adUnits; let clock; before(function () { @@ -1465,7 +1473,6 @@ describe('Unit: Prebid Module', function () { after(function () { clock.restore(); }); - let bidsBackHandlerStub = sinon.stub(); const BIDDER_CODE = 'sampleBidder'; let bids = [{ @@ -1502,11 +1509,12 @@ describe('Unit: Prebid Module', function () { 'start': 1000 }]; + let spec, indexStub, auction, completeAuction; + beforeEach(function () { logMessageSpy = sinon.spy(utils, 'logMessage'); makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests'); makeRequestsStub.returns(bidRequests); - adUnits = [{ code: 'adUnit-code', mediaTypes: { @@ -1514,45 +1522,53 @@ describe('Unit: Prebid Module', function () { sizes: [[300, 250]] } }, + transactionId: 'mock-tid', bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }]; - let adUnitCodes = ['adUnit-code']; - let auction = auctionModule.newAuction({ - adUnits, - adUnitCodes, - callback: bidsBackHandlerStub, - cbTimeout: 2000 - }); - let createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); - }); - - afterEach(function () { - clock.restore(); - adapterManager.makeBidRequests.restore(); - auctionModule.newAuction.restore(); - utils.logMessage.restore(); - }); - - it('should execute callback after timeout', function () { - let spec = { + indexStub = sinon.stub(auctionManager, 'index'); + indexStub.get(() => stubAuctionIndex({adUnits, bidRequests})) + sinon.stub(adapterManager, 'callBids').callsFake((_, bidrequests, addBidResponse, adapterDone) => { + completeAuction = (bidsReceived) => { + bidsReceived.forEach((bid) => addBidResponse(bid.adUnitCode, Object.assign(createBid(), bid))); + bidRequests.forEach((req) => adapterDone.call(req)); + } + }) + const origNewAuction = auctionModule.newAuction; + sinon.stub(auctionModule, 'newAuction').callsFake(function (opts) { + auction = origNewAuction(opts); + return auction; + }) + spec = { code: BIDDER_CODE, isBidRequestValid: sinon.stub(), buildRequests: sinon.stub(), interpretResponse: sinon.stub(), getUserSyncs: sinon.stub(), - onTimeout: sinon.stub() + onTimeout: sinon.stub(), + onSetTargeting: sinon.stub(), }; registerBidder(spec); spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); spec.isBidRequestValid.returns(true); spec.interpretResponse.returns(bids); + }); + + afterEach(function () { + clock.restore(); + adapterManager.makeBidRequests.restore(); + adapterManager.callBids.restore(); + indexStub.restore(); + auction.getBidsReceived = () => []; + auctionModule.newAuction.restore(); + utils.logMessage.restore(); + }); + it('should execute callback after timeout', function () { let requestObj = { - bidsBackHandler: null, // does not need to be defined because of newAuction mock in beforeEach + bidsBackHandler: sinon.stub(), timeout: 2000, adUnits: adUnits }; @@ -1565,26 +1581,13 @@ describe('Unit: Prebid Module', function () { clock.tick(1); assert.ok(logMessageSpy.calledWith(sinon.match(re)), 'executeCallback called'); - expect(bidsBackHandlerStub.getCall(0).args[1]).to.equal(true, + expect(requestObj.bidsBackHandler.getCall(0).args[1]).to.equal(true, 'bidsBackHandler should be called with timedOut=true'); sinon.assert.called(spec.onTimeout); }); - it('should execute callback after setTargeting', function () { - let spec = { - code: BIDDER_CODE, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - onSetTargeting: sinon.stub() - }; - - registerBidder(spec); - spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]); - spec.isBidRequestValid.returns(true); - spec.interpretResponse.returns(bids); - + it('should execute `onSetTargeting` after setTargetingForGPTAsync', function () { const bidId = 1; const auctionId = 1; let adResponse = Object.assign({ @@ -1593,6 +1596,7 @@ describe('Unit: Prebid Module', function () { width: 300, height: 250, adUnitCode: bidRequests[0].bids[0].adUnitCode, + transactionId: 'mock-tid', adserverTargeting: { 'hb_bidder': BIDDER_CODE, 'hb_adid': bidId, @@ -1601,20 +1605,102 @@ describe('Unit: Prebid Module', function () { }, bidder: bids[0].bidderCode, }, bids[0]); - auction.getBidsReceived = function() { return [adResponse]; } - auction.getAuctionId = () => auctionId; let requestObj = { - bidsBackHandler: null, // does not need to be defined because of newAuction mock in beforeEach + bidsBackHandler: null, timeout: 2000, adUnits: adUnits }; $$PREBID_GLOBAL$$.requestBids(requestObj); + completeAuction([adResponse]); $$PREBID_GLOBAL$$.setTargetingForGPTAsync(); sinon.assert.called(spec.onSetTargeting); }); + + describe('returns a promise that resolves', () => { + function delayHook(next, ...args) { + setTimeout(() => next(...args)) + } + + beforeEach(() => { + // make sure the return value works correctly when hooks give up priority + $$PREBID_GLOBAL$$.requestBids.before(delayHook) + }); + + afterEach(() => { + $$PREBID_GLOBAL$$.requestBids.getHooks({hook: delayHook}).remove(); + }); + + Object.entries({ + 'immediately, without bidsBackHandler': (req) => $$PREBID_GLOBAL$$.requestBids(req), + 'after bidsBackHandler': (() => { + const bidsBackHandler = sinon.stub(); + return function (req) { + return $$PREBID_GLOBAL$$.requestBids({...req, bidsBackHandler}).then(({bids, timedOut, auctionId}) => { + sinon.assert.calledWith(bidsBackHandler, bids, timedOut, auctionId); + return {bids, timedOut, auctionId}; + }) + } + })(), + 'after a bidsBackHandler that throws': (req) => $$PREBID_GLOBAL$$.requestBids({...req, bidsBackHandler: () => { throw new Error() }}) + }).forEach(([t, requestBids]) => { + describe(t, () => { + it('with no args, when no adUnits are defined', () => { + return requestBids({}).then((res) => { + expect(res).to.eql({ + bids: undefined, + timedOut: undefined, + auctionId: undefined + }); + }); + }); + + it('on timeout', (done) => { + requestBids({ + auctionId: 'mock-auctionId', + adUnits, + timeout: 10 + }).then(({timedOut, bids, auctionId}) => { + expect(timedOut).to.be.true; + expect(bids).to.eql({}); + expect(auctionId).to.eql('mock-auctionId'); + done(); + }); + clock.tick(12); + }); + + it('with auction result', (done) => { + const bid = { + bidder: 'mock-bidder', + adUnitCode: adUnits[0].code, + transactionId: adUnits[0].transactionId + } + requestBids({ + adUnits, + }).then(({bids}) => { + sinon.assert.match(bids[bid.adUnitCode].bids[0], bid) + done(); + }); + // `completeAuction` won't work until we're out of `delayHook` + // and the mocked auction has been set up; + // setTimeout here takes us after the setTimeout in `delayHook` + setTimeout(() => completeAuction([bid])); + }) + }) + }) + }) + + it('should transfer ttlBuffer to adUnit.ttlBuffer', () => { + $$PREBID_GLOBAL$$.requestBids({ + ttlBuffer: 123, + adUnits: [adUnits[0], {...adUnits[0], ttlBuffer: 0}] + }); + sinon.assert.calledWithMatch(auctionModule.newAuction, { + adUnits: sinon.match((units) => units[0].ttlBuffer === 123 && units[1].ttlBuffer === 0) + }) + }); }) describe('requestBids', function () { @@ -1668,39 +1754,72 @@ describe('Unit: Prebid Module', function () { configObj.resetConfig(); }); - it('passing global and auction-level FPD as ortb2Fragments.global', () => { - configObj.setConfig({ - ortb2: { + describe('with FPD', () => { + let globalFPD, auctionFPD, mergedFPD; + beforeEach(() => { + globalFPD = { 'k1': 'v1', 'k2': { 'k3': 'v3', 'k4': 'v4' } - } - }); - $$PREBID_GLOBAL$$.requestBids({ - ortb2: { + }; + auctionFPD = { + 'k5': 'v5', + 'k2': { + 'k3': 'override', + 'k7': 'v7' + } + }; + mergedFPD = { + 'k1': 'v1', 'k5': 'v5', 'k2': { 'k3': 'override', + 'k4': 'v4', 'k7': 'v7' } + }; + }); + + it('merged from setConfig and requestBids', () => { + configObj.setConfig({ortb2: globalFPD}); + $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + ortb2Fragments: {global: mergedFPD} + })); + }); + + it('enriched through enrichFPD', () => { + function enrich(next, fpd) { + next.bail(fpd.then(ortb2 => { + ortb2.enrich = true; + return ortb2; + })) + } + enrichFPD.before(enrich); + try { + configObj.setConfig({ortb2: globalFPD}); + $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + ortb2Fragments: {global: {...mergedFPD, enrich: true}} + })); + } finally { + enrichFPD.getHooks({hook: enrich}).remove(); } + }) + }); + + it('filtering adUnits by adUnitCodes', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [{code: 'one'}, {code: 'two'}], + adUnitCodes: 'two' }); sinon.assert.calledWith(startAuctionStub, sinon.match({ - ortb2Fragments: { - global: { - 'k1': 'v1', - 'k5': 'v5', - 'k2': { - 'k3': 'override', - 'k4': 'v4', - 'k7': 'v7' - } - } - } - })) + adUnits: [{code: 'two'}] + })); }); + it('passing bidder-specific FPD as ortb2Fragments.bidder', () => { configObj.setBidderConfig({ bidders: ['bidderA', 'bidderC'], @@ -1760,6 +1879,7 @@ describe('Unit: Prebid Module', function () { mediaTypes: {banner: {sizes: [[300, 250]]}}, bids: [{bidder: 'bd'}] }], + adUnitCodes: ['au'], ortb2Fragments }); sinon.assert.calledWith(newAuctionStub, sinon.match({ @@ -2897,6 +3017,20 @@ describe('Unit: Prebid Module', function () { }); }); + describe('aliasRegistry', function () { + it('should return the same value as adapterManager.aliasRegistry by default', function () { + const adapterManagerAliasRegistry = adapterManager.aliasRegistry; + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(adapterManagerAliasRegistry, pbjsAliasRegistry); + }); + + it('should return undefined if the aliasRegistry config option is set to private', function () { + configObj.setConfig({ aliasRegistry: 'private' }); + const pbjsAliasRegistry = $$PREBID_GLOBAL$$.aliasRegistry; + assert.equal(pbjsAliasRegistry, undefined); + }); + }); + describe('setPriceGranularity', function () { it('should log error when not passed granularity', function () { const logErrorSpy = sinon.spy(utils, 'logError'); diff --git a/test/spec/unit/utils/cpm_spec.js b/test/spec/unit/utils/cpm_spec.js new file mode 100644 index 00000000000..9d104b04d09 --- /dev/null +++ b/test/spec/unit/utils/cpm_spec.js @@ -0,0 +1,64 @@ +import {adjustCpm} from '../../../../src/utils/cpm.js'; + +describe('adjustCpm', () => { + const bidderCode = 'mockBidder'; + let adjustmentFn, bs, index; + beforeEach(() => { + bs = { + get: sinon.stub() + } + index = { + getBidRequest: sinon.stub() + } + adjustmentFn = sinon.stub().callsFake((cpm) => cpm * 2); + }) + + it('throws when neither bidRequest nor bidResponse are provided', () => { + expect(() => adjustCpm(1)).to.throw(); + }) + + it('always provides an object as bidResponse for the adjustment fn', () => { + bs.get.callsFake(() => adjustmentFn); + adjustCpm(1, null, {bidder: bidderCode}, {index, bs}); + sinon.assert.calledWith(adjustmentFn, 1, {}); + }); + + describe('when no bidRequest is provided', () => { + Object.entries({ + 'unavailable': undefined, + 'found': {foo: 'bar'} + }).forEach(([t, req]) => { + describe(`and it is ${t} in the index`, () => { + beforeEach(() => { + bs.get.callsFake(() => adjustmentFn); + index.getBidRequest.callsFake(() => req) + }); + + it('provides it to the adjustment fn', () => { + const bidResponse = {bidderCode}; + adjustCpm(1, bidResponse, undefined, {index, bs}); + sinon.assert.calledWith(index.getBidRequest, bidResponse); + sinon.assert.calledWith(adjustmentFn, 1, bidResponse, req); + }) + }) + }) + }); + + Object.entries({ + 'bidResponse': [{bidderCode}], + 'bidRequest': [null, {bidder: bidderCode}], + }).forEach(([t, [bidResp, bidReq]]) => { + describe(`when passed ${t}`, () => { + beforeEach(() => { + bs.get.callsFake((bidder) => { if (bidder === bidderCode) return adjustmentFn }); + }); + it('retrieves the correct bidder code', () => { + expect(adjustCpm(1, bidResp, bidReq, {bs, index})).to.eql(2); + }); + it('passes them to the adjustment fn', () => { + adjustCpm(1, bidResp, bidReq, {bs, index}); + sinon.assert.calledWith(adjustmentFn, 1, bidResp == null ? sinon.match.any : bidResp, bidReq); + }); + }); + }) +}); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 500f479355d..9199b259ad6 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -2,7 +2,7 @@ import { getAdServerTargeting } from 'test/fixtures/fixtures.js'; import { expect } from 'chai'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; -import {deepEqual, waitForElementToLoad} from 'src/utils.js'; +import {memoize, deepEqual, waitForElementToLoad} from 'src/utils.js'; var assert = require('assert'); @@ -1260,3 +1260,47 @@ describe('Utils', function () { }); }); }); + +describe('memoize', () => { + let fn; + + beforeEach(() => { + fn = sinon.stub().callsFake(function() { + return Array.from(arguments); + }); + }); + + it('delegates to fn', () => { + expect(memoize(fn)('one', 'two')).to.eql(['one', 'two']); + }); + + it('caches result after first call, if first argument is the same', () => { + const mem = memoize(fn); + mem('one', 'two'); + expect(mem('one', 'three')).to.eql(['one', 'two']); + expect(fn.callCount).to.equal(1); + }); + + it('delegates again when the first argument changes', () => { + const mem = memoize(fn); + mem('one', 'two'); + expect(mem('two', 'one')).to.eql(['two', 'one']); + expect(fn.callCount).to.eql(2); + }); + + it('can clear cache with .clear', () => { + const mem = memoize(fn); + mem('arg'); + mem.clear(); + expect(mem('arg')).to.eql(['arg']); + expect(fn.callCount).to.equal(2); + }); + + it('allows setting cache keys', () => { + const mem = memoize(fn, (...args) => args.join(',')) + mem('one', 'two'); + mem('one', 'three'); + expect(mem('one', 'three')).to.eql(['one', 'three']); + expect(fn.callCount).to.eql(2); + }) +})